├── .gitignore ├── .npmignore ├── index.js ├── nodemon.json ├── tslint.json ├── tsconfig.json ├── lib ├── Bind.js ├── Media.js ├── Options.js ├── Controller.js ├── PlayerEvent.js ├── MediaQueue.js └── Chromecast.js ├── src ├── Bind.ts ├── Options.ts ├── Media.ts ├── Controller.ts ├── PlayerEvent.ts ├── MediaQueue.ts └── Chromecast.ts ├── package.json ├── static ├── index.html └── script.js ├── README.md └── index.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dts 3 | test 4 | docs 5 | lib -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | serve.js 3 | index.ts 4 | test 5 | static 6 | nodemon.json 7 | dts 8 | tslint.json 9 | docs -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var cc = require('./lib/Chromecast') 4 | var co = require('./lib/Controller') 5 | 6 | module.exports = { 7 | Chromecast: cc.default, 8 | CastOptions: cc.CastOptions, 9 | Controller: co.default 10 | } 11 | 12 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "exec": "browserify ./test/main.js -o ./static/script.js && node ./test/serve.js", 3 | "delay": "500", 4 | "watch": [ 5 | "./lib", 6 | "index.js", 7 | "./test" 8 | ], 9 | "ext": "js", 10 | "env": { 11 | "NODE_ENV": "development", 12 | "PORT": "9009" 13 | } 14 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "jsx-wrap-multiline": false, 4 | "semicolon": [true, "never"], 5 | "class-name": true, 6 | "import-spacing": true, 7 | "new-parens": true, 8 | "eofline": true, 9 | "no-boolean-literal-compare": [true], 10 | "no-consecutive-blank-lines": true, 11 | "no-trailing-whitespace": true, 12 | "quotemark": [true, "single", "jsx-double"] 13 | } 14 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "lib": ["es2017", "dom"], 6 | "sourceMap": false, 7 | "importHelpers": true, 8 | "emitDecoratorMetadata": false, 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "removeComments": true, 13 | "allowUnusedLabels": true, 14 | "strict": false, 15 | "noUnusedLocals": false, 16 | "noUnusedParameters": false, 17 | "strictPropertyInitialization": false, 18 | "outDir": "./lib", 19 | "declaration": true, 20 | "declarationDir": "./dts" 21 | }, 22 | "include": [ 23 | "./src/**/*" 24 | ] 25 | } -------------------------------------------------------------------------------- /lib/Bind.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | function Bind(target, propertyKey, descriptor) { 4 | if (!descriptor || (typeof descriptor.value !== 'function')) { 5 | throw new TypeError("Only methods can be decorated with @Bind. <".concat(propertyKey, "> is not a method!")); 6 | } 7 | return { 8 | configurable: true, 9 | get: function () { 10 | var bound = descriptor.value.bind(this); 11 | Object.defineProperty(this, propertyKey, { 12 | value: bound, 13 | configurable: true, 14 | writable: true 15 | }); 16 | return bound; 17 | } 18 | }; 19 | } 20 | exports.default = Bind; 21 | -------------------------------------------------------------------------------- /src/Bind.ts: -------------------------------------------------------------------------------- 1 | function Bind(target: object, propertyKey: string, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor | void { 2 | if (!descriptor || (typeof descriptor.value !== 'function')) { 3 | throw new TypeError(`Only methods can be decorated with @Bind. <${propertyKey}> is not a method!`) 4 | } 5 | 6 | return { 7 | configurable: true, 8 | get(this: T): T { 9 | const bound: T = descriptor.value!.bind(this) 10 | // Credits to https://github.com/andreypopp/autobind-decorator for memoizing the result of bind against a symbol on the instance. 11 | Object.defineProperty(this, propertyKey, { 12 | value: bound, 13 | configurable: true, 14 | writable: true 15 | }) 16 | return bound 17 | } 18 | } 19 | } 20 | 21 | export default Bind 22 | -------------------------------------------------------------------------------- /src/Options.ts: -------------------------------------------------------------------------------- 1 | 2 | export enum AutoJoinPolicy { 3 | CUSTOM_CONTROLLER_SCOPED = 'custom_controller_scoped', 4 | TAB_AND_ORIGIN_SCOPED = 'tab_and_origin_scoped', 5 | ORIGIN_SCOPED = 'origin_scoped', 6 | PAGE_SCOPED = 'page_scoped' 7 | } 8 | 9 | export interface Options { 10 | autoJoinPolicy?: AutoJoinPolicy 11 | receiverApplicationId?: string 12 | language?: string 13 | } 14 | 15 | export class CastOptions { 16 | private _options: cast.framework.CastOptions 17 | constructor() { 18 | this._options = { 19 | autoJoinPolicy: chrome.cast.AutoJoinPolicy.TAB_AND_ORIGIN_SCOPED, 20 | receiverApplicationId: 'CC1AD845', 21 | language: 'en', 22 | resumeSavedSession: false 23 | } 24 | } 25 | 26 | setOptions(options: Options) { 27 | this._options = { ...this._options, ...options } 28 | } 29 | 30 | get options(): cast.framework.CastOptions { 31 | return this._options 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nolawnchairs/chromecast", 3 | "version": "0.4.5", 4 | "description": "Chromecast Abstraction", 5 | "main": "index.js", 6 | "author": "Micheel Wieczorek", 7 | "license": "MIT", 8 | "directories": { 9 | "lib": "lib" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/nolawnchairs/chromecast-js" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/nolawnchairs/chromecast-js/issues" 17 | }, 18 | "typescript": { 19 | "definition": "./index.d.ts" 20 | }, 21 | "keywords": [ 22 | "google-cast", 23 | "chromecast" 24 | ], 25 | "devDependencies": { 26 | "@types/chrome": "0.0.76", 27 | "@types/chromecast-caf-sender": "^1.0.3", 28 | "@types/node": "^10.12.18", 29 | "express": "^4.16.4", 30 | "nodemon": "^2.0.15", 31 | "tslib": "^2.3.1", 32 | "typescript": "^4.5.5" 33 | }, 34 | "scripts": { 35 | "start": "nodemon", 36 | "dts": "dts-bundle-generator index.ts -o types.d.ts", 37 | "npm-publish": "npm publish --access public" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Chromecast Test 7 | 8 | 19 | 20 | 21 |

Chromecast Test

22 |
23 | 24 |
25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 |
36 | 37 | 38 |
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /lib/Media.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var MediaImpl = (function () { 4 | function MediaImpl() { 5 | this._imageProps = null; 6 | } 7 | MediaImpl.prototype.setDefaultImageProperties = function (w, h) { 8 | this._imageProps = { w: w, h: h }; 9 | }; 10 | MediaImpl.prototype.newEntity = function (mediaId, mimeType, title, image, meta) { 11 | var media = new chrome.cast.media.MediaInfo(mediaId, mimeType); 12 | var metadata = new chrome.cast.media.GenericMediaMetadata(); 13 | metadata.title = title || null; 14 | if (!!image) { 15 | var i = new chrome.cast.Image(image); 16 | if (this._imageProps) { 17 | i.width = this._imageProps.w; 18 | i.height = this._imageProps.h; 19 | } 20 | metadata.images = []; 21 | metadata.images.push(i); 22 | } 23 | if (!!title) { 24 | metadata.title = title; 25 | } 26 | media.metadata = metadata; 27 | if (!!meta && (!image || !title)) { 28 | media.metadata = meta; 29 | } 30 | return media; 31 | }; 32 | return MediaImpl; 33 | }()); 34 | var Media = new MediaImpl(); 35 | exports.default = Media; 36 | -------------------------------------------------------------------------------- /lib/Options.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.CastOptions = exports.AutoJoinPolicy = void 0; 4 | var tslib_1 = require("tslib"); 5 | var AutoJoinPolicy; 6 | (function (AutoJoinPolicy) { 7 | AutoJoinPolicy["CUSTOM_CONTROLLER_SCOPED"] = "custom_controller_scoped"; 8 | AutoJoinPolicy["TAB_AND_ORIGIN_SCOPED"] = "tab_and_origin_scoped"; 9 | AutoJoinPolicy["ORIGIN_SCOPED"] = "origin_scoped"; 10 | AutoJoinPolicy["PAGE_SCOPED"] = "page_scoped"; 11 | })(AutoJoinPolicy = exports.AutoJoinPolicy || (exports.AutoJoinPolicy = {})); 12 | var CastOptions = (function () { 13 | function CastOptions() { 14 | this._options = { 15 | autoJoinPolicy: chrome.cast.AutoJoinPolicy.TAB_AND_ORIGIN_SCOPED, 16 | receiverApplicationId: 'CC1AD845', 17 | language: 'en', 18 | resumeSavedSession: false 19 | }; 20 | } 21 | CastOptions.prototype.setOptions = function (options) { 22 | this._options = (0, tslib_1.__assign)((0, tslib_1.__assign)({}, this._options), options); 23 | }; 24 | Object.defineProperty(CastOptions.prototype, "options", { 25 | get: function () { 26 | return this._options; 27 | }, 28 | enumerable: false, 29 | configurable: true 30 | }); 31 | return CastOptions; 32 | }()); 33 | exports.CastOptions = CastOptions; 34 | -------------------------------------------------------------------------------- /src/Media.ts: -------------------------------------------------------------------------------- 1 | 2 | export type AbstractMetaData = chrome.cast.media.GenericMediaMetadata 3 | | chrome.cast.media.MovieMediaMetadata 4 | | chrome.cast.media.MusicTrackMediaMetadata 5 | | chrome.cast.media.PhotoMediaMetadata 6 | 7 | export interface MediaOptions { 8 | mediaId: string 9 | mimeType: string 10 | title?: string 11 | image?: string 12 | meta?: AbstractMetaData 13 | } 14 | 15 | class MediaImpl { 16 | 17 | private _imageProps: { w: number, h: number } = null 18 | 19 | setDefaultImageProperties(w: number, h: number) { 20 | this._imageProps = { w, h } 21 | } 22 | 23 | newEntity(mediaId: string, mimeType: string, title?: string, image?: string, meta?: AbstractMetaData): chrome.cast.media.MediaInfo { 24 | const media = new chrome.cast.media.MediaInfo(mediaId, mimeType) 25 | const metadata = new chrome.cast.media.GenericMediaMetadata() 26 | metadata.title = title || null 27 | if (!!image) { 28 | const i = new chrome.cast.Image(image) 29 | if (this._imageProps) { 30 | i.width = this._imageProps.w 31 | i.height = this._imageProps.h 32 | } 33 | metadata.images = [] 34 | metadata.images.push(i) 35 | } 36 | if (!!title) { 37 | metadata.title = title 38 | } 39 | 40 | media.metadata = metadata 41 | 42 | if (!!meta && (!image || !title)) { 43 | media.metadata = meta 44 | } 45 | 46 | return media 47 | } 48 | } 49 | 50 | const Media = new MediaImpl() 51 | export default Media 52 | -------------------------------------------------------------------------------- /src/Controller.ts: -------------------------------------------------------------------------------- 1 | import Chromecast from './Chromecast' 2 | 3 | class MediaControllerInstance { 4 | 5 | togglePlay() { 6 | Chromecast.controller.playOrPause() 7 | } 8 | 9 | toggleMute() { 10 | Chromecast.controller.muteOrUnmute() 11 | } 12 | 13 | seek(seconds: number) { 14 | Chromecast.player.currentTime = Chromecast.player.currentTime + seconds 15 | Chromecast.controller.seek() 16 | } 17 | 18 | seekToTime(seconds: number) { 19 | const { duration } = Chromecast.player 20 | if (seconds > duration) 21 | throw new Error(`Controller::seekToTime - Cannot seek beyond duration bounds; max value is ${duration}`) 22 | Chromecast.player.currentTime = seconds 23 | Chromecast.controller.seek() 24 | } 25 | 26 | /** 27 | * Seeks to a percentage of media 28 | * @param ratio percentage of media to seek to, as float value 0 to 1 29 | */ 30 | seekToPercentage(ratio: number) { 31 | const { duration } = Chromecast.player 32 | if (ratio > 1) 33 | throw new Error('Controller::seekToPercentage - Ratio value must be a floating point number between 0 and 1') 34 | Chromecast.player.currentTime = duration * ratio 35 | Chromecast.controller.seek() 36 | } 37 | 38 | stop(drainQueue?: boolean) { 39 | if (drainQueue) { 40 | Chromecast.queue.drain() 41 | } 42 | Chromecast.controller.stop() 43 | } 44 | 45 | adjustVolume(volume: number) { 46 | if (volume > 1) 47 | throw new Error('Controller::adjustVolume - Volume value must be a floating point number between 0 and 1') 48 | Chromecast.player.volumeLevel = volume 49 | Chromecast.controller.setVolumeLevel() 50 | } 51 | 52 | rewind() { 53 | this.seekToTime(0) 54 | } 55 | } 56 | 57 | const Controller = new MediaControllerInstance() 58 | export default Controller 59 | -------------------------------------------------------------------------------- /lib/Controller.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var tslib_1 = require("tslib"); 4 | var Chromecast_1 = (0, tslib_1.__importDefault)(require("./Chromecast")); 5 | var MediaControllerInstance = (function () { 6 | function MediaControllerInstance() { 7 | } 8 | MediaControllerInstance.prototype.togglePlay = function () { 9 | Chromecast_1.default.controller.playOrPause(); 10 | }; 11 | MediaControllerInstance.prototype.toggleMute = function () { 12 | Chromecast_1.default.controller.muteOrUnmute(); 13 | }; 14 | MediaControllerInstance.prototype.seek = function (seconds) { 15 | Chromecast_1.default.player.currentTime = Chromecast_1.default.player.currentTime + seconds; 16 | Chromecast_1.default.controller.seek(); 17 | }; 18 | MediaControllerInstance.prototype.seekToTime = function (seconds) { 19 | var duration = Chromecast_1.default.player.duration; 20 | if (seconds > duration) 21 | throw new Error("Controller::seekToTime - Cannot seek beyond duration bounds; max value is ".concat(duration)); 22 | Chromecast_1.default.player.currentTime = seconds; 23 | Chromecast_1.default.controller.seek(); 24 | }; 25 | MediaControllerInstance.prototype.seekToPercentage = function (ratio) { 26 | var duration = Chromecast_1.default.player.duration; 27 | if (ratio > 1) 28 | throw new Error('Controller::seekToPercentage - Ratio value must be a floating point number between 0 and 1'); 29 | Chromecast_1.default.player.currentTime = duration * ratio; 30 | Chromecast_1.default.controller.seek(); 31 | }; 32 | MediaControllerInstance.prototype.stop = function (drainQueue) { 33 | if (drainQueue) { 34 | Chromecast_1.default.queue.drain(); 35 | } 36 | Chromecast_1.default.controller.stop(); 37 | }; 38 | MediaControllerInstance.prototype.adjustVolume = function (volume) { 39 | if (volume > 1) 40 | throw new Error('Controller::adjustVolume - Volume value must be a floating point number between 0 and 1'); 41 | Chromecast_1.default.player.volumeLevel = volume; 42 | Chromecast_1.default.controller.setVolumeLevel(); 43 | }; 44 | MediaControllerInstance.prototype.rewind = function () { 45 | this.seekToTime(0); 46 | }; 47 | return MediaControllerInstance; 48 | }()); 49 | var Controller = new MediaControllerInstance(); 50 | exports.default = Controller; 51 | -------------------------------------------------------------------------------- /src/PlayerEvent.ts: -------------------------------------------------------------------------------- 1 | import Bind from './Bind' 2 | import MediaQueue, { QueueEventType } from './MediaQueue' 3 | 4 | export type HandlerFn = (value: any) => void 5 | 6 | export interface QueueEvent { 7 | onStarted(): void 8 | onStopped(): void 9 | onUpdated(): void 10 | onItemChanged(item: number): void 11 | } 12 | 13 | export type EventType = 'isConnected' | 'isMediaLoaded' | 'duration' 14 | | 'currentTime' | 'isPaused' | 'volumeLevel' | 'canControlVolume' 15 | | 'isMuted' | 'canPause' | 'canSeek' | 'displayName' | 'statusText' 16 | | 'title' | 'displayStatus' | 'imageUrl' 17 | | 'mediaInfo' | 'playerState' | QueueEventType 18 | 19 | export class PlayerEventDelegate { 20 | 21 | private _queue: MediaQueue 22 | private _mediaCompleteListener: () => void 23 | private _listeners: Map = new Map() 24 | private _allHandlers: (event: EventType, value: any) => void = null 25 | private _queueListeners: Map = new Map() 26 | 27 | constructor(queue: MediaQueue) { 28 | this._queue = queue 29 | this.bind('isMediaLoaded', this.isMediaLoaded) 30 | //this.bind('mediaInfo', this.onMediaInfoChanged) 31 | this.bind('playerState', this.onPlayerState) 32 | this.bind('queueStart', this.onQueueStart) 33 | this.bind('queueComplete', this.onQueueStopped) 34 | this.bind('queueInsert', this.onQueueUpdated) 35 | this.bind('queueRemove', this.onQueueUpdated) 36 | this.bind('queueUpdate', this.onQueueUpdated) 37 | this.bind('queueItem', this.onQueueItemChanged) 38 | 39 | } 40 | 41 | private bind(id: EventType, handler: (value: any) => void) { 42 | this._listeners.set(id, handler) 43 | } 44 | 45 | removeAll() { 46 | this._listeners.clear() 47 | this._queueListeners.clear() 48 | this._allHandlers = null 49 | } 50 | 51 | addListener(event: EventType, handler: HandlerFn) { 52 | this._listeners.set(event, handler) 53 | } 54 | 55 | removeListener(event: EventType) { 56 | this._listeners.delete(event) 57 | } 58 | 59 | setMediaCompleteListener(listener: () => void) { 60 | this._mediaCompleteListener = listener 61 | } 62 | 63 | setAnyEventListener(listener: (event: EventType, value: any) => void) { 64 | this._allHandlers = listener 65 | } 66 | 67 | invoke(eventId: EventType, value: any) { 68 | if (this._allHandlers) { 69 | this._allHandlers(eventId, value) 70 | } 71 | if (this._listeners.has(eventId)) { 72 | this._listeners.get(eventId)(value) 73 | } 74 | } 75 | 76 | @Bind 77 | private onPlayerState(state: string) { 78 | if (state == null) { 79 | this._queueListeners.forEach(l => l.onStopped()) 80 | } 81 | } 82 | 83 | // @Bind 84 | // private isMediaLoaded(is: boolean) { 85 | // if (is) { 86 | // this._castListeners.forEach(l => l.onMediaLoaded()) 87 | // } else { 88 | // this._castListeners.forEach(l => l.onMediaUnloaded()) 89 | // if (!this._queue.isNextItemUserSelected()) 90 | // this._mediaCompleteListener() 91 | // } 92 | // } 93 | 94 | @Bind 95 | private isMediaLoaded(is: boolean) { 96 | if (!is && !this._queue.isNextItemUserSelected()) { 97 | this._mediaCompleteListener() 98 | } 99 | } 100 | 101 | @Bind 102 | private onQueueStart() { 103 | this._queueListeners.forEach(l => l.onStarted()) 104 | } 105 | 106 | @Bind 107 | private onQueueStopped() { 108 | this._queueListeners.forEach(l => l.onStopped()) 109 | } 110 | 111 | @Bind 112 | private onQueueUpdated() { 113 | this._queueListeners.forEach(l => l.onUpdated()) 114 | } 115 | 116 | @Bind 117 | private onQueueItemChanged(item: number) { 118 | this._queueListeners.forEach(l => l.onItemChanged(item)) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/PlayerEvent.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.PlayerEventDelegate = void 0; 4 | var tslib_1 = require("tslib"); 5 | var Bind_1 = (0, tslib_1.__importDefault)(require("./Bind")); 6 | var PlayerEventDelegate = (function () { 7 | function PlayerEventDelegate(queue) { 8 | this._listeners = new Map(); 9 | this._allHandlers = null; 10 | this._queueListeners = new Map(); 11 | this._queue = queue; 12 | this.bind('isMediaLoaded', this.isMediaLoaded); 13 | this.bind('playerState', this.onPlayerState); 14 | this.bind('queueStart', this.onQueueStart); 15 | this.bind('queueComplete', this.onQueueStopped); 16 | this.bind('queueInsert', this.onQueueUpdated); 17 | this.bind('queueRemove', this.onQueueUpdated); 18 | this.bind('queueUpdate', this.onQueueUpdated); 19 | this.bind('queueItem', this.onQueueItemChanged); 20 | } 21 | PlayerEventDelegate.prototype.bind = function (id, handler) { 22 | this._listeners.set(id, handler); 23 | }; 24 | PlayerEventDelegate.prototype.removeAll = function () { 25 | this._listeners.clear(); 26 | this._queueListeners.clear(); 27 | this._allHandlers = null; 28 | }; 29 | PlayerEventDelegate.prototype.addListener = function (event, handler) { 30 | this._listeners.set(event, handler); 31 | }; 32 | PlayerEventDelegate.prototype.removeListener = function (event) { 33 | this._listeners.delete(event); 34 | }; 35 | PlayerEventDelegate.prototype.setMediaCompleteListener = function (listener) { 36 | this._mediaCompleteListener = listener; 37 | }; 38 | PlayerEventDelegate.prototype.setAnyEventListener = function (listener) { 39 | this._allHandlers = listener; 40 | }; 41 | PlayerEventDelegate.prototype.invoke = function (eventId, value) { 42 | if (this._allHandlers) { 43 | this._allHandlers(eventId, value); 44 | } 45 | if (this._listeners.has(eventId)) { 46 | this._listeners.get(eventId)(value); 47 | } 48 | }; 49 | PlayerEventDelegate.prototype.onPlayerState = function (state) { 50 | if (state == null) { 51 | this._queueListeners.forEach(function (l) { return l.onStopped(); }); 52 | } 53 | }; 54 | PlayerEventDelegate.prototype.isMediaLoaded = function (is) { 55 | if (!is && !this._queue.isNextItemUserSelected()) { 56 | this._mediaCompleteListener(); 57 | } 58 | }; 59 | PlayerEventDelegate.prototype.onQueueStart = function () { 60 | this._queueListeners.forEach(function (l) { return l.onStarted(); }); 61 | }; 62 | PlayerEventDelegate.prototype.onQueueStopped = function () { 63 | this._queueListeners.forEach(function (l) { return l.onStopped(); }); 64 | }; 65 | PlayerEventDelegate.prototype.onQueueUpdated = function () { 66 | this._queueListeners.forEach(function (l) { return l.onUpdated(); }); 67 | }; 68 | PlayerEventDelegate.prototype.onQueueItemChanged = function (item) { 69 | this._queueListeners.forEach(function (l) { return l.onItemChanged(item); }); 70 | }; 71 | (0, tslib_1.__decorate)([ 72 | Bind_1.default 73 | ], PlayerEventDelegate.prototype, "onPlayerState", null); 74 | (0, tslib_1.__decorate)([ 75 | Bind_1.default 76 | ], PlayerEventDelegate.prototype, "isMediaLoaded", null); 77 | (0, tslib_1.__decorate)([ 78 | Bind_1.default 79 | ], PlayerEventDelegate.prototype, "onQueueStart", null); 80 | (0, tslib_1.__decorate)([ 81 | Bind_1.default 82 | ], PlayerEventDelegate.prototype, "onQueueStopped", null); 83 | (0, tslib_1.__decorate)([ 84 | Bind_1.default 85 | ], PlayerEventDelegate.prototype, "onQueueUpdated", null); 86 | (0, tslib_1.__decorate)([ 87 | Bind_1.default 88 | ], PlayerEventDelegate.prototype, "onQueueItemChanged", null); 89 | return PlayerEventDelegate; 90 | }()); 91 | exports.PlayerEventDelegate = PlayerEventDelegate; 92 | -------------------------------------------------------------------------------- /lib/MediaQueue.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var tslib_1 = require("tslib"); 4 | var ItemChangeActor; 5 | (function (ItemChangeActor) { 6 | ItemChangeActor[ItemChangeActor["Automatic"] = 0] = "Automatic"; 7 | ItemChangeActor[ItemChangeActor["User"] = 1] = "User"; 8 | })(ItemChangeActor || (ItemChangeActor = {})); 9 | var MediaQueue = (function () { 10 | function MediaQueue() { 11 | this._started = false; 12 | this._items = []; 13 | this._currentItem = -1; 14 | this._nextActor = ItemChangeActor.Automatic; 15 | } 16 | Object.defineProperty(MediaQueue.prototype, "started", { 17 | get: function () { 18 | return this._started; 19 | }, 20 | enumerable: false, 21 | configurable: true 22 | }); 23 | Object.defineProperty(MediaQueue.prototype, "items", { 24 | get: function () { 25 | return this._items; 26 | }, 27 | enumerable: false, 28 | configurable: true 29 | }); 30 | Object.defineProperty(MediaQueue.prototype, "length", { 31 | get: function () { 32 | return this._items.length; 33 | }, 34 | enumerable: false, 35 | configurable: true 36 | }); 37 | Object.defineProperty(MediaQueue.prototype, "currentItem", { 38 | get: function () { 39 | return this._currentItem; 40 | }, 41 | enumerable: false, 42 | configurable: true 43 | }); 44 | Object.defineProperty(MediaQueue.prototype, "current", { 45 | get: function () { 46 | return this._items[this._currentItem]; 47 | }, 48 | enumerable: false, 49 | configurable: true 50 | }); 51 | MediaQueue.prototype.start = function () { 52 | this._started = true; 53 | this._currentItem = 0; 54 | }; 55 | MediaQueue.prototype.resume = function (state) { 56 | this._items = state.items; 57 | this._currentItem = state.currentItem; 58 | this._started = true; 59 | }; 60 | MediaQueue.prototype.isNextItemUserSelected = function () { 61 | return this._nextActor == ItemChangeActor.User; 62 | }; 63 | MediaQueue.prototype.setNextItemUserSelected = function (is) { 64 | this._nextActor = is ? ItemChangeActor.User : ItemChangeActor.Automatic; 65 | }; 66 | MediaQueue.prototype.end = function (clear) { 67 | this._started = false; 68 | if (clear) 69 | this.clear(); 70 | }; 71 | MediaQueue.prototype.next = function (isUserAction) { 72 | if (isUserAction === void 0) { isUserAction = false; } 73 | if (this._currentItem < this.length) { 74 | var nextItem = this._currentItem + 1; 75 | this._currentItem = nextItem; 76 | if (isUserAction) 77 | this._nextActor = ItemChangeActor.User; 78 | return this._items[nextItem]; 79 | } 80 | return null; 81 | }; 82 | MediaQueue.prototype.previous = function () { 83 | if (this._currentItem > 0) { 84 | var nextItem = this._currentItem - 1; 85 | this._currentItem = nextItem; 86 | this._nextActor = ItemChangeActor.User; 87 | return this._items[nextItem]; 88 | } 89 | return null; 90 | }; 91 | MediaQueue.prototype.drain = function () { 92 | this._items.splice(this._currentItem + 1, this._items.length - this._currentItem); 93 | }; 94 | MediaQueue.prototype.clear = function () { 95 | this._started = false; 96 | this._currentItem = -1; 97 | this._items = []; 98 | }; 99 | MediaQueue.prototype.add = function (items) { 100 | var _a; 101 | (_a = this._items).push.apply(_a, items); 102 | }; 103 | MediaQueue.prototype.append = function (item) { 104 | this._items.push(item); 105 | }; 106 | MediaQueue.prototype.removeItem = function (id) { 107 | this._items.splice(id, 1); 108 | return id == this._currentItem; 109 | }; 110 | MediaQueue.prototype.reorderItem = function (from, to) { 111 | var _a; 112 | (_a = this._items).splice.apply(_a, (0, tslib_1.__spreadArray)([to, 0], this._items.splice(from, 1), false)); 113 | }; 114 | return MediaQueue; 115 | }()); 116 | exports.default = MediaQueue; 117 | -------------------------------------------------------------------------------- /src/MediaQueue.ts: -------------------------------------------------------------------------------- 1 | 2 | export declare type QueueEventType = 'queueStart' 3 | | 'queueComplete' 4 | | 'queueInsert' 5 | | 'queueRemove' 6 | | 'queueItem' 7 | | 'queueUpdate' 8 | 9 | enum ItemChangeActor { Automatic, User } 10 | 11 | export interface ResumeState { 12 | items: chrome.cast.media.MediaInfo[] 13 | currentItem: number 14 | } 15 | 16 | export default class MediaQueue { 17 | 18 | private _started: boolean = false 19 | private _items: chrome.cast.media.MediaInfo[] = [] 20 | private _currentItem: number = -1 21 | private _nextActor: ItemChangeActor = ItemChangeActor.Automatic 22 | 23 | /** 24 | * Whether or not the queue has been started 25 | */ 26 | get started(): boolean { 27 | return this._started 28 | } 29 | 30 | /** 31 | * Get all queued items 32 | */ 33 | get items(): chrome.cast.media.MediaInfo[] { 34 | return this._items 35 | } 36 | 37 | /** 38 | * Get the length of item array 39 | */ 40 | get length(): number { 41 | return this._items.length 42 | } 43 | 44 | /** 45 | * Get the currently playing item index 46 | */ 47 | get currentItem(): number { 48 | return this._currentItem 49 | } 50 | 51 | /** 52 | * Get the currently playing item 53 | */ 54 | get current(): chrome.cast.media.MediaInfo { 55 | return this._items[this._currentItem] 56 | } 57 | 58 | /** 59 | * Set queue as started 60 | */ 61 | start() { 62 | this._started = true 63 | this._currentItem = 0 64 | } 65 | 66 | resume(state: ResumeState) { 67 | this._items = state.items 68 | this._currentItem = state.currentItem 69 | this._started = true 70 | } 71 | 72 | /** 73 | * Get whether or not next queue item is 74 | * user-requested or automatic 75 | */ 76 | isNextItemUserSelected(): boolean { 77 | return this._nextActor == ItemChangeActor.User 78 | } 79 | 80 | /** 81 | * Set whether or not next item is a user action 82 | * @param is is a user action 83 | */ 84 | setNextItemUserSelected(is: boolean) { 85 | this._nextActor = is ? ItemChangeActor.User : ItemChangeActor.Automatic 86 | } 87 | 88 | /** 89 | * Set queue as completed 90 | * @param clear whether or not to reset the queue 91 | */ 92 | end(clear?: boolean) { 93 | this._started = false 94 | if (clear) 95 | this.clear() 96 | } 97 | 98 | /** 99 | * Returns the next item in the queue and advances 100 | * the current item cursor 101 | * @nullable 102 | */ 103 | next(isUserAction: boolean = false): chrome.cast.media.MediaInfo | null { 104 | if (this._currentItem < this.length) { 105 | const nextItem = this._currentItem + 1 106 | this._currentItem = nextItem 107 | if (isUserAction) 108 | this._nextActor = ItemChangeActor.User 109 | return this._items[nextItem] 110 | } 111 | return null 112 | } 113 | 114 | /** 115 | * Returns the previous item in the queue and rewinds 116 | * the current item cursor 117 | * @nullable 118 | */ 119 | previous(): chrome.cast.media.MediaInfo | null { 120 | if (this._currentItem > 0) { 121 | const nextItem = this._currentItem - 1 122 | this._currentItem = nextItem 123 | this._nextActor = ItemChangeActor.User 124 | return this._items[nextItem] 125 | } 126 | return null 127 | } 128 | 129 | /** 130 | * Remove all items that come after 131 | * the currently playing item 132 | */ 133 | drain() { 134 | this._items.splice(this._currentItem + 1, this._items.length - this._currentItem) 135 | } 136 | 137 | /** 138 | * Reset the queue 139 | */ 140 | clear() { 141 | this._started = false 142 | this._currentItem = -1 143 | this._items = [] 144 | } 145 | 146 | /** 147 | * Add items to the queue 148 | * @param items array of MediaInfo items 149 | */ 150 | add(items: chrome.cast.media.MediaInfo[]) { 151 | this._items.push(...items) 152 | } 153 | 154 | /** 155 | * Append an item to the queue 156 | * @param item MediaInfo item 157 | */ 158 | append(item: chrome.cast.media.MediaInfo) { 159 | this._items.push(item) 160 | } 161 | 162 | /** 163 | * Remove an item from the queue 164 | * @param id index of queue item 165 | */ 166 | removeItem(id: number): boolean { 167 | this._items.splice(id, 1) 168 | return id == this._currentItem 169 | } 170 | 171 | /** 172 | * Moves a queue item from one position to another 173 | * @param from index of item being moved 174 | * @param to index to place the moved item 175 | */ 176 | reorderItem(from: number, to: number) { 177 | this._items.splice(to, 0, ...this._items.splice(from, 1)) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chromecast Sender API 2 | 3 | This is an abstraction wrapper around Google's Cast Sender API for Chrome. Google's [documentation](https://developers.google.com/cast/docs/developers) for the Chrome API is pretty shitty. Sure, all the massive JS doc is there, but a lot of aspects of using it are not covered in the Guides section... it pretty much tells you how to connect and load media, and how to do more advanced things like subtitles, but it lacks any clues on how to implement queuing and event handling. Google also doesn't offer the code on NPM, so there's that. 4 | 5 | I started this project because I manage two video-based web applications that I wanted to use the cast API for, and I figured a few people may find it useful. 6 | 7 | Since this is more or less an *abstraction* around Google's native Cast Sender API. As an abstraction, most aspects have been vastly over-simplified. This library lacks (as of now) some of the functionality, but it should be sufficient for most use cases. 8 | 9 | This library is written in [TypeScript](https://www.typescriptlang.org/), with TypeScript in mind. 10 | 11 | > **Note** this is not in any way production-ready at the moment. Use at your own risk. 12 | 13 | ## Install 14 | Install via NPM 15 | 16 | `npm i @nolawnchairs/chromecast` 17 | 18 | Or Yarn... 19 | 20 | `yarn add @nolawnchairs/chromecast` 21 | 22 | ## Getting Started 23 | The package contains the following modules: 24 | 25 | **Chromecast** - The base singleton class that manages the connection, media and queuing 26 | 27 | **Options** - Sets the options for the cast framework 28 | 29 | **Controller** - Controls the media playback 30 | 31 | **Register** - Registering for events 32 | 33 | `import { Chromecast, Options, Controller, Register } from '@nolawnchairs/chromecast` 34 | 35 | ___ 36 | **Quick Setup** 37 | All options are optional, however you must provide a valid `receiverId` in order to test on your Chromecast device. Under the hood, this adds Google's javascript file to your document body, and fires the `readyStateListener` once the dependencies have loaded. 38 | ``` 39 | // Define options 40 | const options = { 41 | receiverApplicationId: receiverId, 42 | autoJoinPolicy: Chromecast.AutoJoinPolicy.ORIGIN_SCOPED 43 | } 44 | 45 | // Initialize chromecast service 46 | Chromecast.initializeCastService(options).catch(console.error) 47 | Chromecast.setReadyStateListner(() => { 48 | // Add your logic here. Anything that uses the cast 49 | // service must be called after the service is ready 50 | }) 51 | ``` 52 | You will also need to add Google's web component to your DOM. This creates the cast icon that triggers the connection with Chrome. 53 | ``` 54 | 55 | ``` 56 | ___ 57 | ### Playing Media 58 | There are two methods of playing media - as a single-play, and as a queue: 59 | 60 | **Single Play** 61 | ``` 62 | // First, create a Media entity 63 | const media = Chromecast.newMediaEntity(myVideoUrl, 'video/mp4') 64 | 65 | // Then play it 66 | Chromecast.playOne(media) 67 | ``` 68 | Simple, eh? Queuing is much better, though... 69 | 70 | **Queuing** 71 | ``` 72 | // Create Media entities. Here, we'll assume that all the values 73 | // are stored as an array 74 | const media = [...] 75 | const queue = media.map(m => { 76 | return Chromecast.newMediaEntity(m.url, m.mime, m.title, m.image) 77 | }) 78 | 79 | // Add these items to the queue 80 | Chromecast.queueItems(queue) 81 | 82 | // Start the queue 83 | Chromecast.startQueue() 84 | 85 | // Skip to the next item in the queue 86 | Chromecast.playNext() 87 | 88 | // Go back to the previous video in the queue 89 | Chromecast.playPrevious() 90 | 91 | // Restart the current item 92 | Chromecast.restartCurrent() 93 | 94 | // Stop current playback and disconnect from the cast service 95 | Chromecast.disconnect() 96 | ``` 97 | ___ 98 | ### Media Control 99 | We want our users to be able to control aspects of playback 100 | ``` 101 | // Toggle play 102 | Controller.togglePlay() 103 | 104 | // Toggle Mute 105 | Controller.toggleMute() 106 | 107 | // Seeking is easy, there are three ways, 108 | // Seek forward or back by a certain number of seconds 109 | Controller.seek(30) 110 | Controller.seek(-10) 111 | 112 | // Seek to a certain time (in seconds) 113 | Controller.seekToTime(187) // 3 minutes, 7 seconds 114 | 115 | // Seek to a percentage of the video (as a float between 0 and 1) 116 | Controller.seekToPercentage(0.5) // 50% 117 | 118 | // Adjust volume (as a float between 0 and 1) 119 | Controller.adjustVolume(0.5) // 50% volume 120 | 121 | // Stop playback 122 | Controller.stop() 123 | ``` 124 | ___ 125 | ### Observing Events 126 | There are different ways to observe events, depending on your coding paradigm 127 | 128 | **Functional** 129 | For our functional programming friends, you can subscribe to events much like the NodeJS way: 130 | ``` 131 | // Listen for the 'currentTime' event, which tells us playback 132 | // progress in seconds 133 | Chromecast.on('currentTime', seconds => doSomething(seconds)) 134 | 135 | // To remove listeners, we just need the name 136 | Chromecast.off('currentTime') 137 | ``` 138 | We can also listen to all events by registering. All `Register` methods return an unregister function that can be called when we're done listening 139 | ``` 140 | // Register for all events 141 | const unregisterHook = Register.forEvents(myEventHandler) 142 | function eventHandler(eventName, eventData) { 143 | console.log(eventName, eventData) 144 | switch (eventName) { 145 | case 'currentTime': 146 | doSomethingWithTime(eventData) 147 | break 148 | } 149 | } 150 | 151 | // unregister all events 152 | unregisterHook() 153 | ``` 154 | **Object Oriented** 155 | This is for the TypeScript people who prefer to create a class with an implemented interface. These, for the most part are the same listeners as above, but are aliased as interface methods 156 | ``` 157 | import { Listeners, Register } from '@nolawnchairs/chromecast' 158 | import Store from './MyStore' 159 | 160 | class PlaybackHandler implements Listeners.PlaybackEvent { 161 | // Fired when 'currentTime' is emitted 162 | onTimeUpdate(time: number) { 163 | Store.setPlaybackTime(time) 164 | } 165 | // Fired when media is paused 166 | onPaused() { 167 | Store.setPlaybackPaused() 168 | } 169 | ... 170 | } 171 | 172 | // Register 173 | const unregister = Register.forPlaybackEvents(new PlaybackHandler()) 174 | 175 | // Unregister 176 | unregister() 177 | ``` 178 | Of course, this way, you must implement all methods in the interface contract. -------------------------------------------------------------------------------- /lib/Chromecast.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var tslib_1 = require("tslib"); 4 | var Bind_1 = (0, tslib_1.__importDefault)(require("./Bind")); 5 | var PlayerEvent_1 = require("./PlayerEvent"); 6 | var Media_1 = (0, tslib_1.__importDefault)(require("./Media")); 7 | var MediaQueue_1 = (0, tslib_1.__importDefault)(require("./MediaQueue")); 8 | var Options_1 = require("./Options"); 9 | var onAvailableCallbackId = '__onGCastApiAvailable'; 10 | var ChromecastInstance = (function () { 11 | function ChromecastInstance() { 12 | this._ready = false; 13 | this.AutoJoinPolicy = { 14 | CUSTOM_CONTROLLER_SCOPED: 'custom_controller_scoped', 15 | TAB_AND_ORIGIN_SCOPED: 'tab_and_origin_scoped', 16 | ORIGIN_SCOPED: 'origin_scoped', 17 | PAGE_SCOPED: 'page_scoped' 18 | }; 19 | this._queue = new MediaQueue_1.default(); 20 | this._eventDelegate = new PlayerEvent_1.PlayerEventDelegate(this._queue); 21 | this._eventDelegate.setMediaCompleteListener(this.onMediaOrganicallyCompleted); 22 | } 23 | ChromecastInstance.prototype.isReady = function () { 24 | return this._ready; 25 | }; 26 | ChromecastInstance.prototype.initializeCastService = function (options) { 27 | var _this = this; 28 | return new Promise(function (resolve, reject) { 29 | if (typeof chrome.cast === 'undefined') { 30 | var t = document.createElement('script'); 31 | t.src = 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1'; 32 | document.body.appendChild(t); 33 | window[onAvailableCallbackId] = function (available) { 34 | if (available) { 35 | _this._context = cast.framework.CastContext.getInstance(); 36 | _this._context.addEventListener(cast.framework.CastContextEventType.SESSION_STATE_CHANGED, _this.onSessionStateChange); 37 | _this._options = new Options_1.CastOptions(); 38 | if (options) 39 | _this._options.setOptions(options); 40 | _this._context.setOptions(_this._options.options); 41 | resolve(); 42 | } 43 | else { 44 | reject(new Error('Cast service is not available')); 45 | } 46 | }; 47 | } 48 | }); 49 | }; 50 | Object.defineProperty(ChromecastInstance.prototype, "eventDelegate", { 51 | get: function () { 52 | return this._eventDelegate; 53 | }, 54 | enumerable: false, 55 | configurable: true 56 | }); 57 | Object.defineProperty(ChromecastInstance.prototype, "controller", { 58 | get: function () { 59 | return this._controller; 60 | }, 61 | enumerable: false, 62 | configurable: true 63 | }); 64 | Object.defineProperty(ChromecastInstance.prototype, "player", { 65 | get: function () { 66 | return this._player; 67 | }, 68 | enumerable: false, 69 | configurable: true 70 | }); 71 | Object.defineProperty(ChromecastInstance.prototype, "queue", { 72 | get: function () { 73 | return this._queue; 74 | }, 75 | enumerable: false, 76 | configurable: true 77 | }); 78 | ChromecastInstance.prototype.setStartingStateListener = function (listener) { 79 | this._startingStateListener = listener; 80 | }; 81 | ChromecastInstance.prototype.setReadyStateListner = function (listener) { 82 | this._readyStateListener = listener; 83 | }; 84 | ChromecastInstance.prototype.setShutownStateListener = function (listener) { 85 | this._shutdownStateListener = listener; 86 | }; 87 | ChromecastInstance.prototype.setResumeStateListener = function (listener) { 88 | this._resumedStateListener = listener; 89 | }; 90 | ChromecastInstance.prototype.setErrorListener = function (listener) { 91 | this._errorListener = listener; 92 | }; 93 | ChromecastInstance.prototype.disconnect = function () { 94 | this._castSession.endSession(true); 95 | this._queue.clear(); 96 | this.removeListeners(); 97 | this._ready = false; 98 | }; 99 | ChromecastInstance.prototype.on = function (event, fn) { 100 | this._eventDelegate.addListener(event, fn); 101 | }; 102 | ChromecastInstance.prototype.off = function (event) { 103 | this._eventDelegate.removeListener(event); 104 | }; 105 | ChromecastInstance.prototype.onAnyEvent = function (listener) { 106 | this._eventDelegate.setAnyEventListener(listener); 107 | }; 108 | ChromecastInstance.prototype.unregisterAll = function () { 109 | this._eventDelegate.removeAll(); 110 | }; 111 | ChromecastInstance.prototype.newMediaEntity = function (mediaId, mimeType, title, image, meta) { 112 | return Media_1.default.newEntity(mediaId, mimeType, title, image, meta); 113 | }; 114 | ChromecastInstance.prototype.enqueue = function (item) { 115 | this.enqueueItems([item]); 116 | }; 117 | ChromecastInstance.prototype.enqueueItems = function (items) { 118 | this._queue.add(items); 119 | }; 120 | ChromecastInstance.prototype.appendToQueue = function (item) { 121 | if (!this._queue.started) 122 | throw new Error('Chromecast::appendToQueue - Items cannot be appended before queued media has begun playback. Add your media items using Chromecast::queueItems before starting playback'); 123 | this._queue.append(item); 124 | this.emitQueueEvent('queueInsert', this._queue.items); 125 | }; 126 | ChromecastInstance.prototype.removeFromQueue = function (item) { 127 | if (!this._queue.started) 128 | throw new Error('Chromecast::removeFromQueue - Items cannot be removed before queued media has begun playback.'); 129 | if (item >= this._queue.length) 130 | throw new Error("Chromecast::removeFromQueue - Index out of bounds, attempt to reference index ".concat(item, " of ").concat(this._queue.length, " items")); 131 | if (this._queue.removeItem(item)) { 132 | this._controller.stop(); 133 | this.loadItem(this._queue.current); 134 | } 135 | this.emitQueueEvent('queueRemove', this._queue.items); 136 | }; 137 | ChromecastInstance.prototype.reorderQueue = function (from, to) { 138 | if (!this._queue.started) 139 | throw new Error('Chromecast::redorderQueue - Items cannot be reordered before queued media has begun playback.'); 140 | if (from >= this._queue.length - 1) 141 | throw new Error("Chromecast::reorderQueue - Index out of bounds, attempt to reference index ".concat(from, " of ").concat(this._queue.length, " items")); 142 | this._queue.reorderItem(from, to); 143 | this.emitQueueEvent('queueUpdate', this._queue.items); 144 | }; 145 | ChromecastInstance.prototype.clearQueue = function () { 146 | if (this._queue.started) 147 | this._controller.stop(); 148 | this._queue.clear(); 149 | this.emitQueueEvent('queueUpdate', this._queue.items); 150 | }; 151 | ChromecastInstance.prototype.startQueue = function (startingTime) { 152 | var _this = this; 153 | if (startingTime === void 0) { startingTime = 0; } 154 | if (this._queue.length == 0) 155 | throw new Error('Chromecast::startQueue - No items in queue'); 156 | var item = this._queue.next(); 157 | if (item) { 158 | this.loadItem(item, false, startingTime) 159 | .then(function () { 160 | _this._queue.start(); 161 | _this.emitQueueEvent('queueItem', 0); 162 | }) 163 | .catch(this.onMediaLoadError); 164 | } 165 | }; 166 | ChromecastInstance.prototype.advance = function (isUserAction) { 167 | if (isUserAction === void 0) { isUserAction = false; } 168 | var item = this._queue.next(isUserAction); 169 | if (item) { 170 | this.loadItem(item, isUserAction).catch(this.onMediaLoadError); 171 | this.emitQueueEvent('queueItem', this._queue.currentItem); 172 | } 173 | }; 174 | ChromecastInstance.prototype.playNext = function () { 175 | this.advance(true); 176 | }; 177 | ChromecastInstance.prototype.playPrevious = function () { 178 | this._controller.stop(); 179 | var item = this._queue.previous(); 180 | if (item) { 181 | this.loadItem(item, true); 182 | this.emitQueueEvent('queueItem', this._queue.currentItem); 183 | } 184 | }; 185 | ChromecastInstance.prototype.getCastDeviceName = function () { 186 | return this._castSession.getCastDevice().friendlyName; 187 | }; 188 | ChromecastInstance.prototype.createController = function () { 189 | this.removeListeners(); 190 | this._controller = new cast.framework.RemotePlayerController(this._player); 191 | this._controller.addEventListener(cast.framework.RemotePlayerEventType.ANY_CHANGE, this.onPlayerEvent); 192 | }; 193 | ChromecastInstance.prototype.loadItem = function (item, fromUser, startTime) { 194 | if (fromUser === void 0) { fromUser = false; } 195 | if (startTime === void 0) { startTime = 0; } 196 | return (0, tslib_1.__awaiter)(this, void 0, void 0, function () { 197 | var request, loadError, timeout; 198 | var _this = this; 199 | return (0, tslib_1.__generator)(this, function (_a) { 200 | switch (_a.label) { 201 | case 0: 202 | request = new chrome.cast.media.LoadRequest(item); 203 | request.currentTime = startTime; 204 | return [4, this._castSession.loadMedia(request)]; 205 | case 1: 206 | loadError = _a.sent(); 207 | this.createController(); 208 | this._queue.setNextItemUserSelected(fromUser); 209 | timeout = window.setTimeout(function () { 210 | _this._queue.setNextItemUserSelected(false); 211 | window.clearTimeout(timeout); 212 | }, 1000); 213 | return [2, loadError ? Promise.reject(loadError) : Promise.resolve()]; 214 | } 215 | }); 216 | }); 217 | }; 218 | ChromecastInstance.prototype.onSessionStateChange = function (event) { 219 | switch (event.sessionState) { 220 | case cast.framework.SessionState.SESSION_STARTING: 221 | this._startingStateListener && this._startingStateListener(); 222 | break; 223 | case cast.framework.SessionState.SESSION_ENDED: 224 | this._shutdownStateListener && this._shutdownStateListener(); 225 | break; 226 | case cast.framework.SessionState.SESSION_STARTED: 227 | this._castSession = this._context.getCurrentSession(); 228 | this._player = new cast.framework.RemotePlayer(); 229 | this._readyStateListener && this._readyStateListener(); 230 | break; 231 | case cast.framework.SessionState.SESSION_RESUMED: 232 | this._castSession = this._context.getCurrentSession(); 233 | this._player = new cast.framework.RemotePlayer(); 234 | this.createController(); 235 | if (this._resumedStateListener) { 236 | var state = this._resumedStateListener(); 237 | if (state.items.length > 0) { 238 | this._queue.resume(state); 239 | } 240 | } 241 | break; 242 | case cast.framework.SessionState.SESSION_START_FAILED: 243 | if (this._errorListener) { 244 | this._errorListener(new chrome.cast.Error(chrome.cast.ErrorCode.RECEIVER_UNAVAILABLE, 'Cast service could not connect')); 245 | } 246 | break; 247 | } 248 | }; 249 | ChromecastInstance.prototype.onMediaOrganicallyCompleted = function () { 250 | this.playNext(); 251 | }; 252 | ChromecastInstance.prototype.onMediaLoadError = function (errorCode) { 253 | if (this._errorListener) 254 | this._errorListener(new chrome.cast.Error(errorCode)); 255 | }; 256 | ChromecastInstance.prototype.onPlayerEvent = function (event) { 257 | this._eventDelegate.invoke(event.field, event.value); 258 | }; 259 | ChromecastInstance.prototype.emitQueueEvent = function (event, value) { 260 | this._eventDelegate.invoke(event, value); 261 | }; 262 | ChromecastInstance.prototype.removeListeners = function () { 263 | if (!!this._controller) 264 | this._controller.removeEventListener(cast.framework.RemotePlayerEventType.ANY_CHANGE, this.onPlayerEvent); 265 | }; 266 | (0, tslib_1.__decorate)([ 267 | Bind_1.default 268 | ], ChromecastInstance.prototype, "onSessionStateChange", null); 269 | (0, tslib_1.__decorate)([ 270 | Bind_1.default 271 | ], ChromecastInstance.prototype, "onMediaOrganicallyCompleted", null); 272 | (0, tslib_1.__decorate)([ 273 | Bind_1.default 274 | ], ChromecastInstance.prototype, "onMediaLoadError", null); 275 | (0, tslib_1.__decorate)([ 276 | Bind_1.default 277 | ], ChromecastInstance.prototype, "onPlayerEvent", null); 278 | return ChromecastInstance; 279 | }()); 280 | var Chromecast = new ChromecastInstance(); 281 | exports.default = Chromecast; 282 | -------------------------------------------------------------------------------- /src/Chromecast.ts: -------------------------------------------------------------------------------- 1 | import Bind from './Bind' 2 | import { PlayerEventDelegate, EventType, HandlerFn } from './PlayerEvent' 3 | import Media, { AbstractMetaData } from './Media' 4 | import MediaQueue, { QueueEventType, ResumeState } from './MediaQueue' 5 | import { CastOptions, Options } from './Options' 6 | 7 | const onAvailableCallbackId = '__onGCastApiAvailable' 8 | 9 | class ChromecastInstance { 10 | 11 | private _ready = false 12 | private _options: CastOptions 13 | private _context: cast.framework.CastContext 14 | private _castSession: cast.framework.CastSession 15 | private _player: cast.framework.RemotePlayer 16 | private _controller: cast.framework.RemotePlayerController 17 | private _eventDelegate: PlayerEventDelegate 18 | private _queue: MediaQueue 19 | private _startingStateListener: () => void 20 | private _readyStateListener: () => void 21 | private _resumedStateListener: () => ResumeState 22 | private _shutdownStateListener: () => void 23 | private _errorListener: (e: chrome.cast.Error) => void 24 | 25 | //#region main 26 | readonly AutoJoinPolicy = { 27 | CUSTOM_CONTROLLER_SCOPED: 'custom_controller_scoped', 28 | TAB_AND_ORIGIN_SCOPED: 'tab_and_origin_scoped', 29 | ORIGIN_SCOPED: 'origin_scoped', 30 | PAGE_SCOPED: 'page_scoped' 31 | } 32 | 33 | constructor() { 34 | this._queue = new MediaQueue() 35 | this._eventDelegate = new PlayerEventDelegate(this._queue) 36 | this._eventDelegate.setMediaCompleteListener(this.onMediaOrganicallyCompleted) 37 | } 38 | 39 | /** 40 | * Determine if the cast framework has loaded and 41 | * is ready for media 42 | */ 43 | isReady(): boolean { 44 | return this._ready 45 | } 46 | 47 | /** 48 | * Initializes the cast framework 49 | * @param options Optional Options to set 50 | */ 51 | initializeCastService(options?: Options): Promise { 52 | return new Promise((resolve, reject) => { 53 | if (typeof chrome.cast === 'undefined') { 54 | const t = document.createElement('script') 55 | t.src = 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1' 56 | document.body.appendChild(t) 57 | window[onAvailableCallbackId] = (available: boolean) => { 58 | if (available) { 59 | this._context = cast.framework.CastContext.getInstance() 60 | this._context.addEventListener(cast.framework.CastContextEventType.SESSION_STATE_CHANGED, 61 | this.onSessionStateChange) 62 | this._options = new CastOptions() 63 | if (options) 64 | this._options.setOptions(options) 65 | this._context.setOptions(this._options.options) 66 | resolve() 67 | } else { 68 | reject(new Error('Cast service is not available')) 69 | } 70 | } 71 | } 72 | }) 73 | } 74 | 75 | /** 76 | * Gets the PlayerEvent delegate 77 | */ 78 | get eventDelegate(): PlayerEventDelegate { 79 | return this._eventDelegate 80 | } 81 | 82 | /** 83 | * Gets the current media controller instance 84 | */ 85 | get controller(): cast.framework.RemotePlayerController { 86 | return this._controller 87 | } 88 | 89 | /** 90 | * Gets the current media player instance 91 | */ 92 | get player(): cast.framework.RemotePlayer { 93 | return this._player 94 | } 95 | 96 | /** 97 | * Gets the current MediaQueue instance 98 | */ 99 | get queue(): MediaQueue { 100 | return this._queue 101 | } 102 | 103 | /** 104 | * Sets the listener for when service connection is starting 105 | * @param listener Listener to be invoked 106 | */ 107 | setStartingStateListener(listener: () => void) { 108 | this._startingStateListener = listener 109 | } 110 | 111 | /** 112 | * Set the listener to be invoked when the framework is loaded 113 | * and ready for media input 114 | * @param listener Listener to be invoked 115 | */ 116 | setReadyStateListner(listener: () => void) { 117 | this._readyStateListener = listener 118 | } 119 | 120 | /** 121 | * Set the listener to be invoked when the media session ends 122 | * @param listener Listener to be invoked 123 | */ 124 | setShutownStateListener(listener: () => void) { 125 | this._shutdownStateListener = listener 126 | } 127 | 128 | /** 129 | * Sets the listener to be invoked when the media session resumes 130 | * @param listener Listener to be invoked 131 | */ 132 | setResumeStateListener(listener: () => ResumeState) { 133 | this._resumedStateListener = listener 134 | } 135 | 136 | /** 137 | * Set the listener to be invoked when an error occurs 138 | * @param listener Listener to be invoked 139 | */ 140 | setErrorListener(listener: (e: chrome.cast.Error) => void) { 141 | this._errorListener = listener 142 | } 143 | //#endregion 144 | 145 | /** 146 | * Disconnect entirely from the cast session 147 | */ 148 | disconnect() { 149 | this._castSession.endSession(true) 150 | this._queue.clear() 151 | this.removeListeners() 152 | this._ready = false 153 | } 154 | 155 | /** 156 | * Adds a listener to a player event 157 | * @param event EventType to add 158 | * @param fn callback function 159 | */ 160 | on(event: EventType, fn: HandlerFn) { 161 | this._eventDelegate.addListener(event, fn) 162 | } 163 | 164 | /** 165 | * Cancels a listener from a player event 166 | * @param event EventType to cancel 167 | */ 168 | off(event: EventType) { 169 | this._eventDelegate.removeListener(event) 170 | } 171 | 172 | /** 173 | * Register a listener for all cast-related events 174 | * @param listener listener for all events 175 | */ 176 | onAnyEvent(listener: (event: EventType, value: any) => void) { 177 | this._eventDelegate.setAnyEventListener(listener) 178 | } 179 | 180 | /** 181 | * Unregister all event handlers 182 | */ 183 | unregisterAll() { 184 | this._eventDelegate.removeAll() 185 | } 186 | 187 | /** 188 | * Create a new MediaInfo entity 189 | * @param mediaId Media resource, usually a URL 190 | * @param mimeType Mime type of media 191 | * @param title optional title of media 192 | * @param image optional image related to media 193 | * @param meta optional meta data for the media 194 | */ 195 | newMediaEntity(mediaId: string, mimeType: string): chrome.cast.media.MediaInfo 196 | newMediaEntity(mediaId: string, mimeType: string, title: string): chrome.cast.media.MediaInfo 197 | newMediaEntity(mediaId: string, mimeType: string, title: string, image: string): chrome.cast.media.MediaInfo 198 | newMediaEntity(mediaId: string, mimeType: string, title?: string, image?: string, meta?: AbstractMetaData): chrome.cast.media.MediaInfo 199 | newMediaEntity(mediaId: string, mimeType: string, title?: string, image?: string, meta?: AbstractMetaData): chrome.cast.media.MediaInfo { 200 | return Media.newEntity(mediaId, mimeType, title, image, meta) 201 | } 202 | 203 | /** 204 | * Load a single item into the play queue 205 | * @param item MediaInfo item 206 | */ 207 | enqueue(item: chrome.cast.media.MediaInfo) { 208 | this.enqueueItems([item]) 209 | } 210 | 211 | /** 212 | * Load multiple items into the play queue 213 | * @param items MediaInfo item array 214 | */ 215 | enqueueItems(items: chrome.cast.media.MediaInfo[]) { 216 | this._queue.add(items) 217 | } 218 | 219 | /** 220 | * Append an item to the end of the play queue. Can only be 221 | * done after media playback has begun 222 | * @param item MediaInfo item 223 | */ 224 | appendToQueue(item: chrome.cast.media.MediaInfo) { 225 | if (!this._queue.started) 226 | throw new Error('Chromecast::appendToQueue - Items cannot be appended before queued media has begun playback. Add your media items using Chromecast::queueItems before starting playback') 227 | this._queue.append(item) 228 | this.emitQueueEvent('queueInsert', this._queue.items) 229 | } 230 | 231 | /** 232 | * Remove an item from the play queue 233 | * @param item Queue index of item being removed 234 | */ 235 | removeFromQueue(item: number) { 236 | if (!this._queue.started) 237 | throw new Error('Chromecast::removeFromQueue - Items cannot be removed before queued media has begun playback.') 238 | if (item >= this._queue.length) 239 | throw new Error(`Chromecast::removeFromQueue - Index out of bounds, attempt to reference index ${item} of ${this._queue.length} items`) 240 | if (this._queue.removeItem(item)) { 241 | this._controller.stop() 242 | this.loadItem(this._queue.current) 243 | } 244 | this.emitQueueEvent('queueRemove', this._queue.items) 245 | } 246 | 247 | /** 248 | * Moves an item to another position in the queue 249 | * @param from Queue index of item being moved 250 | * @param to The new index to move the item to 251 | */ 252 | reorderQueue(from: number, to: number) { 253 | if (!this._queue.started) 254 | throw new Error('Chromecast::redorderQueue - Items cannot be reordered before queued media has begun playback.') 255 | if (from >= this._queue.length - 1) 256 | throw new Error(`Chromecast::reorderQueue - Index out of bounds, attempt to reference index ${from} of ${this._queue.length} items`) 257 | this._queue.reorderItem(from, to) 258 | this.emitQueueEvent('queueUpdate', this._queue.items) 259 | } 260 | 261 | /** 262 | * Clears the play queue entirely 263 | */ 264 | clearQueue() { 265 | if (this._queue.started) 266 | this._controller.stop() 267 | this._queue.clear() 268 | this.emitQueueEvent('queueUpdate', this._queue.items) 269 | } 270 | 271 | /** 272 | * Start the playback queue 273 | * @param startingTime optional starting time of first item. Defaults to 0 274 | */ 275 | startQueue(startingTime: number = 0) { 276 | if (this._queue.length == 0) 277 | throw new Error('Chromecast::startQueue - No items in queue') 278 | const item = this._queue.next() 279 | if (item) { 280 | this.loadItem(item, false, startingTime) 281 | .then(() => { 282 | this._queue.start() 283 | this.emitQueueEvent('queueItem', 0) 284 | }) 285 | .catch(this.onMediaLoadError) 286 | } 287 | } 288 | 289 | /** 290 | * Advance the queue to the next item 291 | * @param isUserAction Whether or not action was performed by the user 292 | */ 293 | advance(isUserAction: boolean = false) { 294 | const item = this._queue.next(isUserAction) 295 | if (item) { 296 | this.loadItem(item, isUserAction).catch(this.onMediaLoadError) 297 | this.emitQueueEvent('queueItem', this._queue.currentItem) 298 | } 299 | } 300 | 301 | /** 302 | * Play the next item in the queue, user requested 303 | */ 304 | playNext() { 305 | this.advance(true) 306 | } 307 | 308 | /** 309 | * Play the previous item in the queue, user requested 310 | */ 311 | playPrevious() { 312 | this._controller.stop() 313 | const item = this._queue.previous() 314 | if (item) { 315 | this.loadItem(item, true) 316 | this.emitQueueEvent('queueItem', this._queue.currentItem) 317 | } 318 | } 319 | 320 | /** 321 | * Get the cast device name 322 | */ 323 | getCastDeviceName(): string { 324 | return this._castSession.getCastDevice().friendlyName 325 | } 326 | 327 | /** 328 | * Create the controller for the currently playing media item 329 | */ 330 | private createController() { 331 | this.removeListeners() 332 | this._controller = new cast.framework.RemotePlayerController(this._player) 333 | this._controller.addEventListener(cast.framework.RemotePlayerEventType.ANY_CHANGE, this.onPlayerEvent) 334 | } 335 | 336 | /** 337 | * Loads and item into the cast session 338 | * @param item MediaInfo item 339 | * @param fromUser if this load is from user requesting next/previous or is organic 340 | * @param startTime optional start time of video 341 | */ 342 | private async loadItem(item: chrome.cast.media.MediaInfo, fromUser: boolean = false, startTime = 0): Promise { 343 | const request = new chrome.cast.media.LoadRequest(item) 344 | request.currentTime = startTime 345 | const loadError = await this._castSession.loadMedia(request) 346 | this.createController() 347 | 348 | // Set the queued item as user input or organic, and reset to organic 349 | // (false) after the video starts playback 350 | this._queue.setNextItemUserSelected(fromUser) 351 | const timeout = window.setTimeout(() => { 352 | this._queue.setNextItemUserSelected(false) 353 | window.clearTimeout(timeout) 354 | }, 1000) 355 | return loadError ? Promise.reject(loadError) : Promise.resolve() 356 | } 357 | 358 | /** 359 | * Called when the session state changes, take appropriate actions 360 | * @param event SessionStateEventData 361 | */ 362 | @Bind 363 | private onSessionStateChange(event: cast.framework.SessionStateEventData) { 364 | switch (event.sessionState) { 365 | case cast.framework.SessionState.SESSION_STARTING: 366 | this._startingStateListener && this._startingStateListener() 367 | break 368 | case cast.framework.SessionState.SESSION_ENDED: 369 | this._shutdownStateListener && this._shutdownStateListener() 370 | break 371 | case cast.framework.SessionState.SESSION_STARTED: 372 | this._castSession = this._context.getCurrentSession() 373 | this._player = new cast.framework.RemotePlayer() 374 | this._readyStateListener && this._readyStateListener() 375 | break 376 | case cast.framework.SessionState.SESSION_RESUMED: 377 | this._castSession = this._context.getCurrentSession() 378 | this._player = new cast.framework.RemotePlayer() 379 | this.createController() 380 | if (this._resumedStateListener) { 381 | const state = this._resumedStateListener() 382 | if (state.items.length > 0) { 383 | this._queue.resume(state) 384 | } 385 | } 386 | break 387 | case cast.framework.SessionState.SESSION_START_FAILED: 388 | if (this._errorListener) { 389 | this._errorListener(new chrome.cast.Error( 390 | chrome.cast.ErrorCode.RECEIVER_UNAVAILABLE, 'Cast service could not connect')) 391 | } 392 | break 393 | } 394 | } 395 | 396 | /** 397 | * Called when the current media item completes oganically, and plays the next 398 | */ 399 | @Bind 400 | private onMediaOrganicallyCompleted() { 401 | this.playNext() 402 | } 403 | 404 | /** 405 | * Handler any media load errors, and delegate to listener 406 | * @param errorCode ErrorCode 407 | */ 408 | @Bind 409 | private onMediaLoadError(errorCode: chrome.cast.ErrorCode) { 410 | if (this._errorListener) 411 | this._errorListener(new chrome.cast.Error(errorCode)) 412 | } 413 | 414 | /** 415 | * Invoked when any player event happens 416 | * delegate event emitting to eventDelegate 417 | * @param event the event 418 | */ 419 | @Bind 420 | private onPlayerEvent(event: cast.framework.RemotePlayerChangedEvent) { 421 | this._eventDelegate.invoke(event.field as EventType, event.value) 422 | } 423 | 424 | /** 425 | * Emit a queue event 426 | * @param event QueueEventType 427 | * @param value Value to send 428 | */ 429 | private emitQueueEvent(event: QueueEventType, value?: any) { 430 | this._eventDelegate.invoke(event as EventType, value) 431 | } 432 | 433 | /** 434 | * Remove listeners from controller 435 | */ 436 | private removeListeners() { 437 | if (!!this._controller) 438 | this._controller.removeEventListener(cast.framework.RemotePlayerEventType.ANY_CHANGE, this.onPlayerEvent) 439 | } 440 | } 441 | 442 | const Chromecast = new ChromecastInstance() 443 | export default Chromecast 444 | -------------------------------------------------------------------------------- /static/script.js: -------------------------------------------------------------------------------- 1 | (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i is not a method!"); 20 | } 21 | return { 22 | configurable: true, 23 | get: function () { 24 | var bound = descriptor.value.bind(this); 25 | Object.defineProperty(this, propertyKey, { 26 | value: bound, 27 | configurable: true, 28 | writable: true 29 | }); 30 | return bound; 31 | } 32 | }; 33 | } 34 | exports.default = Bind; 35 | 36 | },{}],3:[function(require,module,exports){ 37 | "use strict"; 38 | Object.defineProperty(exports, "__esModule", { value: true }); 39 | var tslib_1 = require("tslib"); 40 | var Bind_1 = tslib_1.__importDefault(require("./Bind")); 41 | var PlayerEvent_1 = require("./PlayerEvent"); 42 | var Media_1 = tslib_1.__importDefault(require("./Media")); 43 | var MediaQueue_1 = tslib_1.__importDefault(require("./MediaQueue")); 44 | var Options_1 = require("./Options"); 45 | var onAvailableCallbackId = '__onGCastApiAvailable'; 46 | var ChromecastInstance = (function () { 47 | function ChromecastInstance() { 48 | this._ready = false; 49 | this.AutoJoinPolicy = { 50 | CUSTOM_CONTROLLER_SCOPED: 'custom_controller_scoped', 51 | TAB_AND_ORIGIN_SCOPED: 'tab_and_origin_scoped', 52 | ORIGIN_SCOPED: 'origin_scoped', 53 | PAGE_SCOPED: 'page_scoped' 54 | }; 55 | this._queue = new MediaQueue_1.default(); 56 | this._eventDelegate = new PlayerEvent_1.PlayerEventDelegate(this._queue); 57 | this._eventDelegate.setMediaCompleteListener(this.onMediaOrganicallyCompleted); 58 | } 59 | ChromecastInstance.prototype.isReady = function () { 60 | return this._ready; 61 | }; 62 | ChromecastInstance.prototype.initializeCastService = function (options) { 63 | var _this = this; 64 | return new Promise(function (resolve, reject) { 65 | if (typeof chrome.cast === 'undefined') { 66 | var t = document.createElement('script'); 67 | t.src = 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1'; 68 | document.body.appendChild(t); 69 | window[onAvailableCallbackId] = function (available) { 70 | if (available) { 71 | _this._context = cast.framework.CastContext.getInstance(); 72 | _this._context.addEventListener(cast.framework.CastContextEventType.SESSION_STATE_CHANGED, _this.onSessionStateChange); 73 | _this._options = new Options_1.CastOptions(); 74 | if (options) 75 | _this._options.setOptions(options); 76 | _this._context.setOptions(_this._options.options); 77 | resolve(); 78 | } 79 | else { 80 | reject(new Error('Cast service is not available')); 81 | } 82 | }; 83 | } 84 | }); 85 | }; 86 | Object.defineProperty(ChromecastInstance.prototype, "eventDelegate", { 87 | get: function () { 88 | return this._eventDelegate; 89 | }, 90 | enumerable: true, 91 | configurable: true 92 | }); 93 | Object.defineProperty(ChromecastInstance.prototype, "controller", { 94 | get: function () { 95 | return this._controller; 96 | }, 97 | enumerable: true, 98 | configurable: true 99 | }); 100 | Object.defineProperty(ChromecastInstance.prototype, "player", { 101 | get: function () { 102 | return this._player; 103 | }, 104 | enumerable: true, 105 | configurable: true 106 | }); 107 | Object.defineProperty(ChromecastInstance.prototype, "queue", { 108 | get: function () { 109 | return this._queue; 110 | }, 111 | enumerable: true, 112 | configurable: true 113 | }); 114 | ChromecastInstance.prototype.setStartingStateListener = function (listener) { 115 | this._startingStateListener = listener; 116 | }; 117 | ChromecastInstance.prototype.setReadyStateListner = function (listener) { 118 | this._readyStateListener = listener; 119 | }; 120 | ChromecastInstance.prototype.setShutownStateListener = function (listener) { 121 | this._shutdownStateListener = listener; 122 | }; 123 | ChromecastInstance.prototype.setResumeStateListener = function (listener) { 124 | this._resumedStateListener = listener; 125 | }; 126 | ChromecastInstance.prototype.setErrorListener = function (listener) { 127 | this._errorListener = listener; 128 | }; 129 | ChromecastInstance.prototype.disconnect = function () { 130 | this._castSession.endSession(true); 131 | this._queue.clear(); 132 | this.removeListeners(); 133 | this._ready = false; 134 | }; 135 | ChromecastInstance.prototype.on = function (event, fn) { 136 | this._eventDelegate.addListener(event, fn); 137 | }; 138 | ChromecastInstance.prototype.off = function (event) { 139 | this._eventDelegate.removeListener(event); 140 | }; 141 | ChromecastInstance.prototype.onAnyEvent = function (listener) { 142 | this._eventDelegate.setAnyEventListener(listener); 143 | }; 144 | ChromecastInstance.prototype.unregisterAll = function () { 145 | this._eventDelegate.removeAll(); 146 | }; 147 | ChromecastInstance.prototype.newMediaEntity = function (mediaId, mimeType, title, image, meta) { 148 | return Media_1.default.newEntity(mediaId, mimeType, title, image, meta); 149 | }; 150 | ChromecastInstance.prototype.enqueue = function (item) { 151 | this.enqueueItems([item]); 152 | }; 153 | ChromecastInstance.prototype.enqueueItems = function (items) { 154 | this._queue.add(items); 155 | }; 156 | ChromecastInstance.prototype.appendToQueue = function (item) { 157 | if (!this._queue.started) 158 | throw new Error('Chromecast::appendToQueue - Items cannot be appended before queued media has begun playback. Add your media items using Chromecast::queueItems before starting playback'); 159 | this._queue.append(item); 160 | this.emitQueueEvent('queueInsert', this._queue.items); 161 | }; 162 | ChromecastInstance.prototype.removeFromQueue = function (item) { 163 | if (!this._queue.started) 164 | throw new Error('Chromecast::removeFromQueue - Items cannot be removed before queued media has begun playback.'); 165 | if (item >= this._queue.length) 166 | throw new Error("Chromecast::removeFromQueue - Index out of bounds, attempt to reference index " + item + " of " + this._queue.length + " items"); 167 | if (this._queue.removeItem(item)) { 168 | this._controller.stop(); 169 | this.loadItem(this._queue.current); 170 | } 171 | this.emitQueueEvent('queueRemove', this._queue.items); 172 | }; 173 | ChromecastInstance.prototype.reorderQueue = function (from, to) { 174 | if (!this._queue.started) 175 | throw new Error('Chromecast::redorderQueue - Items cannot be reordered before queued media has begun playback.'); 176 | if (from >= this._queue.length - 1) 177 | throw new Error("Chromecast::reorderQueue - Index out of bounds, attempt to reference index " + from + " of " + this._queue.length + " items"); 178 | this._queue.reorderItem(from, to); 179 | this.emitQueueEvent('queueUpdate', this._queue.items); 180 | }; 181 | ChromecastInstance.prototype.clearQueue = function () { 182 | if (this._queue.started) 183 | this._controller.stop(); 184 | this._queue.clear(); 185 | this.emitQueueEvent('queueUpdate', this._queue.items); 186 | }; 187 | ChromecastInstance.prototype.startQueue = function (startingTime) { 188 | var _this = this; 189 | if (startingTime === void 0) { startingTime = 0; } 190 | if (this._queue.length == 0) 191 | throw new Error('Chromecast::startQueue - No items in queue'); 192 | var item = this._queue.next(); 193 | if (item) { 194 | this.loadItem(item, false, startingTime) 195 | .then(function () { 196 | _this._queue.start(); 197 | _this.emitQueueEvent('queueItem', 0); 198 | }) 199 | .catch(this.onMediaLoadError); 200 | } 201 | }; 202 | ChromecastInstance.prototype.advance = function (isUserAction) { 203 | if (isUserAction === void 0) { isUserAction = false; } 204 | var item = this._queue.next(isUserAction); 205 | if (item) { 206 | this.loadItem(item, isUserAction).catch(this.onMediaLoadError); 207 | this.emitQueueEvent('queueItem', this._queue.currentItem); 208 | } 209 | }; 210 | ChromecastInstance.prototype.playNext = function () { 211 | this.advance(true); 212 | }; 213 | ChromecastInstance.prototype.playPrevious = function () { 214 | this._controller.stop(); 215 | var item = this._queue.previous(); 216 | if (item) { 217 | this.loadItem(item, true); 218 | this.emitQueueEvent('queueItem', this._queue.currentItem); 219 | } 220 | }; 221 | ChromecastInstance.prototype.getCastDeviceName = function () { 222 | return this._castSession.getCastDevice().friendlyName; 223 | }; 224 | ChromecastInstance.prototype.createController = function () { 225 | this.removeListeners(); 226 | this._controller = new cast.framework.RemotePlayerController(this._player); 227 | this._controller.addEventListener(cast.framework.RemotePlayerEventType.ANY_CHANGE, this.onPlayerEvent); 228 | }; 229 | ChromecastInstance.prototype.loadItem = function (item, fromUser, startTime) { 230 | if (fromUser === void 0) { fromUser = false; } 231 | if (startTime === void 0) { startTime = 0; } 232 | return tslib_1.__awaiter(this, void 0, void 0, function () { 233 | var request, loadError, timeout; 234 | var _this = this; 235 | return tslib_1.__generator(this, function (_a) { 236 | switch (_a.label) { 237 | case 0: 238 | request = new chrome.cast.media.LoadRequest(item); 239 | request.currentTime = startTime; 240 | return [4, this._castSession.loadMedia(request)]; 241 | case 1: 242 | loadError = _a.sent(); 243 | this.createController(); 244 | this._queue.setNextItemUserSelected(fromUser); 245 | timeout = window.setTimeout(function () { 246 | _this._queue.setNextItemUserSelected(false); 247 | window.clearTimeout(timeout); 248 | }, 1000); 249 | return [2, loadError ? Promise.reject(loadError) : Promise.resolve()]; 250 | } 251 | }); 252 | }); 253 | }; 254 | ChromecastInstance.prototype.onSessionStateChange = function (event) { 255 | console.log(event.sessionState); 256 | switch (event.sessionState) { 257 | case cast.framework.SessionState.SESSION_STARTING: 258 | this._startingStateListener && this._startingStateListener(); 259 | break; 260 | case cast.framework.SessionState.SESSION_ENDED: 261 | this._shutdownStateListener && this._shutdownStateListener(); 262 | break; 263 | case cast.framework.SessionState.SESSION_STARTED: 264 | this._castSession = this._context.getCurrentSession(); 265 | this._player = new cast.framework.RemotePlayer(); 266 | this._readyStateListener && this._readyStateListener(); 267 | break; 268 | case cast.framework.SessionState.SESSION_RESUMED: 269 | this._castSession = this._context.getCurrentSession(); 270 | this._player = new cast.framework.RemotePlayer(); 271 | this.createController(); 272 | if (this._resumedStateListener) { 273 | var state = this._resumedStateListener(); 274 | this._queue.resume(state); 275 | } 276 | break; 277 | case cast.framework.SessionState.SESSION_START_FAILED: 278 | if (this._errorListener) { 279 | this._errorListener(new chrome.cast.Error(chrome.cast.ErrorCode.RECEIVER_UNAVAILABLE, 'Cast service could not connect')); 280 | } 281 | break; 282 | } 283 | }; 284 | ChromecastInstance.prototype.onMediaOrganicallyCompleted = function () { 285 | this.playNext(); 286 | }; 287 | ChromecastInstance.prototype.onMediaLoadError = function (errorCode) { 288 | if (this._errorListener) 289 | this._errorListener(new chrome.cast.Error(errorCode)); 290 | }; 291 | ChromecastInstance.prototype.onPlayerEvent = function (event) { 292 | this._eventDelegate.invoke(event.field, event.value); 293 | }; 294 | ChromecastInstance.prototype.emitQueueEvent = function (event, value) { 295 | this._eventDelegate.invoke(event, value); 296 | }; 297 | ChromecastInstance.prototype.removeListeners = function () { 298 | if (!!this._controller) 299 | this._controller.removeEventListener(cast.framework.RemotePlayerEventType.ANY_CHANGE, this.onPlayerEvent); 300 | }; 301 | tslib_1.__decorate([ 302 | Bind_1.default 303 | ], ChromecastInstance.prototype, "onSessionStateChange", null); 304 | tslib_1.__decorate([ 305 | Bind_1.default 306 | ], ChromecastInstance.prototype, "onMediaOrganicallyCompleted", null); 307 | tslib_1.__decorate([ 308 | Bind_1.default 309 | ], ChromecastInstance.prototype, "onMediaLoadError", null); 310 | tslib_1.__decorate([ 311 | Bind_1.default 312 | ], ChromecastInstance.prototype, "onPlayerEvent", null); 313 | return ChromecastInstance; 314 | }()); 315 | var Chromecast = new ChromecastInstance(); 316 | exports.default = Chromecast; 317 | 318 | },{"./Bind":2,"./Media":5,"./MediaQueue":6,"./Options":7,"./PlayerEvent":8,"tslib":9}],4:[function(require,module,exports){ 319 | "use strict"; 320 | Object.defineProperty(exports, "__esModule", { value: true }); 321 | var tslib_1 = require("tslib"); 322 | var Chromecast_1 = tslib_1.__importDefault(require("./Chromecast")); 323 | var MediaControllerInstance = (function () { 324 | function MediaControllerInstance() { 325 | } 326 | MediaControllerInstance.prototype.togglePlay = function () { 327 | Chromecast_1.default.controller.playOrPause(); 328 | }; 329 | MediaControllerInstance.prototype.toggleMute = function () { 330 | Chromecast_1.default.controller.muteOrUnmute(); 331 | }; 332 | MediaControllerInstance.prototype.seek = function (seconds) { 333 | Chromecast_1.default.player.currentTime = Chromecast_1.default.player.currentTime + seconds; 334 | Chromecast_1.default.controller.seek(); 335 | }; 336 | MediaControllerInstance.prototype.seekToTime = function (seconds) { 337 | var duration = Chromecast_1.default.player.duration; 338 | if (seconds > duration) 339 | throw new Error("Controller::seekToTime - Cannot seek beyond duration bounds; max value is " + duration); 340 | Chromecast_1.default.player.currentTime = seconds; 341 | Chromecast_1.default.controller.seek(); 342 | }; 343 | MediaControllerInstance.prototype.seekToPercentage = function (ratio) { 344 | var duration = Chromecast_1.default.player.duration; 345 | if (ratio > 1) 346 | throw new Error('Controller::seekToPercentage - Ratio value must be a floating point number between 0 and 1'); 347 | Chromecast_1.default.player.currentTime = duration * ratio; 348 | Chromecast_1.default.controller.seek(); 349 | }; 350 | MediaControllerInstance.prototype.stop = function (drainQueue) { 351 | if (drainQueue) { 352 | Chromecast_1.default.queue.drain(); 353 | } 354 | Chromecast_1.default.controller.stop(); 355 | }; 356 | MediaControllerInstance.prototype.adjustVolume = function (volume) { 357 | if (volume > 1) 358 | throw new Error('Controller::adjustVolume - Volume value must be a floating point number between 0 and 1'); 359 | Chromecast_1.default.player.volumeLevel = volume; 360 | Chromecast_1.default.controller.setVolumeLevel(); 361 | }; 362 | MediaControllerInstance.prototype.rewind = function () { 363 | this.seekToTime(0); 364 | }; 365 | return MediaControllerInstance; 366 | }()); 367 | var Controller = new MediaControllerInstance(); 368 | exports.default = Controller; 369 | 370 | },{"./Chromecast":3,"tslib":9}],5:[function(require,module,exports){ 371 | "use strict"; 372 | Object.defineProperty(exports, "__esModule", { value: true }); 373 | var MediaImpl = (function () { 374 | function MediaImpl() { 375 | this._imageProps = null; 376 | } 377 | MediaImpl.prototype.setDefaultImageProperties = function (w, h) { 378 | this._imageProps = { w: w, h: h }; 379 | }; 380 | MediaImpl.prototype.newEntity = function (mediaId, mimeType, title, image, meta) { 381 | var media = new chrome.cast.media.MediaInfo(mediaId, mimeType); 382 | var metadata = new chrome.cast.media.GenericMediaMetadata(); 383 | metadata.title = title || null; 384 | if (!!image) { 385 | var i = new chrome.cast.Image(image); 386 | if (this._imageProps) { 387 | i.width = this._imageProps.w; 388 | i.height = this._imageProps.h; 389 | } 390 | metadata.images = []; 391 | metadata.images.push(i); 392 | } 393 | if (!!title) { 394 | metadata.title = title; 395 | } 396 | media.metadata = metadata; 397 | if (!!meta && (!image || !title)) { 398 | media.metadata = meta; 399 | } 400 | return media; 401 | }; 402 | return MediaImpl; 403 | }()); 404 | var Media = new MediaImpl(); 405 | exports.default = Media; 406 | 407 | },{}],6:[function(require,module,exports){ 408 | "use strict"; 409 | Object.defineProperty(exports, "__esModule", { value: true }); 410 | var ItemChangeActor; 411 | (function (ItemChangeActor) { 412 | ItemChangeActor[ItemChangeActor["Automatic"] = 0] = "Automatic"; 413 | ItemChangeActor[ItemChangeActor["User"] = 1] = "User"; 414 | })(ItemChangeActor || (ItemChangeActor = {})); 415 | var MediaQueue = (function () { 416 | function MediaQueue() { 417 | this._started = false; 418 | this._items = []; 419 | this._currentItem = -1; 420 | this._nextActor = ItemChangeActor.Automatic; 421 | } 422 | Object.defineProperty(MediaQueue.prototype, "started", { 423 | get: function () { 424 | return this._started; 425 | }, 426 | enumerable: true, 427 | configurable: true 428 | }); 429 | Object.defineProperty(MediaQueue.prototype, "items", { 430 | get: function () { 431 | return this._items; 432 | }, 433 | enumerable: true, 434 | configurable: true 435 | }); 436 | Object.defineProperty(MediaQueue.prototype, "length", { 437 | get: function () { 438 | return this._items.length; 439 | }, 440 | enumerable: true, 441 | configurable: true 442 | }); 443 | Object.defineProperty(MediaQueue.prototype, "currentItem", { 444 | get: function () { 445 | return this._currentItem; 446 | }, 447 | enumerable: true, 448 | configurable: true 449 | }); 450 | Object.defineProperty(MediaQueue.prototype, "current", { 451 | get: function () { 452 | return this._items[this._currentItem]; 453 | }, 454 | enumerable: true, 455 | configurable: true 456 | }); 457 | MediaQueue.prototype.start = function () { 458 | this._started = true; 459 | this._currentItem = 0; 460 | }; 461 | MediaQueue.prototype.resume = function (state) { 462 | this._items = state.items; 463 | this._currentItem = state.currentItem; 464 | this._started = true; 465 | }; 466 | MediaQueue.prototype.isNextItemUserSelected = function () { 467 | return this._nextActor == ItemChangeActor.User; 468 | }; 469 | MediaQueue.prototype.setNextItemUserSelected = function (is) { 470 | this._nextActor = is ? ItemChangeActor.User : ItemChangeActor.Automatic; 471 | }; 472 | MediaQueue.prototype.end = function (clear) { 473 | this._started = false; 474 | if (clear) 475 | this.clear(); 476 | }; 477 | MediaQueue.prototype.next = function (isUserAction) { 478 | if (isUserAction === void 0) { isUserAction = false; } 479 | if (this._currentItem < this.length) { 480 | var nextItem = this._currentItem + 1; 481 | this._currentItem = nextItem; 482 | if (isUserAction) 483 | this._nextActor = ItemChangeActor.User; 484 | return this._items[nextItem]; 485 | } 486 | return null; 487 | }; 488 | MediaQueue.prototype.previous = function () { 489 | if (this._currentItem > 0) { 490 | var nextItem = this._currentItem - 1; 491 | this._currentItem = nextItem; 492 | this._nextActor = ItemChangeActor.User; 493 | return this._items[nextItem]; 494 | } 495 | return null; 496 | }; 497 | MediaQueue.prototype.drain = function () { 498 | this._items.splice(this._currentItem + 1, this._items.length - this._currentItem); 499 | }; 500 | MediaQueue.prototype.clear = function () { 501 | this._started = false; 502 | this._currentItem = -1; 503 | this._items = []; 504 | }; 505 | MediaQueue.prototype.add = function (items) { 506 | var _a; 507 | (_a = this._items).push.apply(_a, items); 508 | }; 509 | MediaQueue.prototype.append = function (item) { 510 | this._items.push(item); 511 | }; 512 | MediaQueue.prototype.removeItem = function (id) { 513 | this._items.splice(id, 1); 514 | return id == this._currentItem; 515 | }; 516 | MediaQueue.prototype.reorderItem = function (from, to) { 517 | var _a; 518 | (_a = this._items).splice.apply(_a, [to, 0].concat(this._items.splice(from, 1))); 519 | }; 520 | return MediaQueue; 521 | }()); 522 | exports.default = MediaQueue; 523 | 524 | },{}],7:[function(require,module,exports){ 525 | "use strict"; 526 | Object.defineProperty(exports, "__esModule", { value: true }); 527 | var tslib_1 = require("tslib"); 528 | var AutoJoinPolicy; 529 | (function (AutoJoinPolicy) { 530 | AutoJoinPolicy["CUSTOM_CONTROLLER_SCOPED"] = "custom_controller_scoped"; 531 | AutoJoinPolicy["TAB_AND_ORIGIN_SCOPED"] = "tab_and_origin_scoped"; 532 | AutoJoinPolicy["ORIGIN_SCOPED"] = "origin_scoped"; 533 | AutoJoinPolicy["PAGE_SCOPED"] = "page_scoped"; 534 | })(AutoJoinPolicy = exports.AutoJoinPolicy || (exports.AutoJoinPolicy = {})); 535 | var CastOptions = (function () { 536 | function CastOptions() { 537 | this._options = { 538 | autoJoinPolicy: chrome.cast.AutoJoinPolicy.TAB_AND_ORIGIN_SCOPED, 539 | receiverApplicationId: 'CC1AD845', 540 | language: 'en', 541 | resumeSavedSession: false 542 | }; 543 | } 544 | CastOptions.prototype.setOptions = function (options) { 545 | this._options = tslib_1.__assign({}, this._options, options); 546 | }; 547 | Object.defineProperty(CastOptions.prototype, "options", { 548 | get: function () { 549 | return this._options; 550 | }, 551 | enumerable: true, 552 | configurable: true 553 | }); 554 | return CastOptions; 555 | }()); 556 | exports.CastOptions = CastOptions; 557 | 558 | },{"tslib":9}],8:[function(require,module,exports){ 559 | "use strict"; 560 | Object.defineProperty(exports, "__esModule", { value: true }); 561 | var tslib_1 = require("tslib"); 562 | var Bind_1 = tslib_1.__importDefault(require("./Bind")); 563 | var PlayerEventDelegate = (function () { 564 | function PlayerEventDelegate(queue) { 565 | this._listeners = new Map(); 566 | this._allHandlers = null; 567 | this._queueListeners = new Map(); 568 | this._queue = queue; 569 | this.bind('isMediaLoaded', this.isMediaLoaded); 570 | this.bind('playerState', this.onPlayerState); 571 | this.bind('queueStart', this.onQueueStart); 572 | this.bind('queueComplete', this.onQueueStopped); 573 | this.bind('queueInsert', this.onQueueUpdated); 574 | this.bind('queueRemove', this.onQueueUpdated); 575 | this.bind('queueUpdate', this.onQueueUpdated); 576 | this.bind('queueItem', this.onQueueItemChanged); 577 | } 578 | PlayerEventDelegate.prototype.bind = function (id, handler) { 579 | this._listeners.set(id, handler); 580 | }; 581 | PlayerEventDelegate.prototype.removeAll = function () { 582 | this._listeners.clear(); 583 | this._queueListeners.clear(); 584 | this._allHandlers = null; 585 | }; 586 | PlayerEventDelegate.prototype.addListener = function (event, handler) { 587 | this._listeners.set(event, handler); 588 | }; 589 | PlayerEventDelegate.prototype.removeListener = function (event) { 590 | this._listeners.delete(event); 591 | }; 592 | PlayerEventDelegate.prototype.setMediaCompleteListener = function (listener) { 593 | this._mediaCompleteListener = listener; 594 | }; 595 | PlayerEventDelegate.prototype.setAnyEventListener = function (listener) { 596 | this._allHandlers = listener; 597 | }; 598 | PlayerEventDelegate.prototype.invoke = function (eventId, value) { 599 | if (this._allHandlers) { 600 | this._allHandlers(eventId, value); 601 | } 602 | if (this._listeners.has(eventId)) { 603 | this._listeners.get(eventId)(value); 604 | } 605 | }; 606 | PlayerEventDelegate.prototype.onPlayerState = function (state) { 607 | if (state == null) { 608 | this._queueListeners.forEach(function (l) { return l.onStopped(); }); 609 | } 610 | }; 611 | PlayerEventDelegate.prototype.isMediaLoaded = function (is) { 612 | if (!is && !this._queue.isNextItemUserSelected()) { 613 | this._mediaCompleteListener(); 614 | } 615 | }; 616 | PlayerEventDelegate.prototype.onQueueStart = function () { 617 | this._queueListeners.forEach(function (l) { return l.onStarted(); }); 618 | }; 619 | PlayerEventDelegate.prototype.onQueueStopped = function () { 620 | this._queueListeners.forEach(function (l) { return l.onStopped(); }); 621 | }; 622 | PlayerEventDelegate.prototype.onQueueUpdated = function () { 623 | this._queueListeners.forEach(function (l) { return l.onUpdated(); }); 624 | }; 625 | PlayerEventDelegate.prototype.onQueueItemChanged = function (item) { 626 | this._queueListeners.forEach(function (l) { return l.onItemChanged(item); }); 627 | }; 628 | tslib_1.__decorate([ 629 | Bind_1.default 630 | ], PlayerEventDelegate.prototype, "onPlayerState", null); 631 | tslib_1.__decorate([ 632 | Bind_1.default 633 | ], PlayerEventDelegate.prototype, "isMediaLoaded", null); 634 | tslib_1.__decorate([ 635 | Bind_1.default 636 | ], PlayerEventDelegate.prototype, "onQueueStart", null); 637 | tslib_1.__decorate([ 638 | Bind_1.default 639 | ], PlayerEventDelegate.prototype, "onQueueStopped", null); 640 | tslib_1.__decorate([ 641 | Bind_1.default 642 | ], PlayerEventDelegate.prototype, "onQueueUpdated", null); 643 | tslib_1.__decorate([ 644 | Bind_1.default 645 | ], PlayerEventDelegate.prototype, "onQueueItemChanged", null); 646 | return PlayerEventDelegate; 647 | }()); 648 | exports.PlayerEventDelegate = PlayerEventDelegate; 649 | 650 | },{"./Bind":2,"tslib":9}],9:[function(require,module,exports){ 651 | (function (global){ 652 | /*! ***************************************************************************** 653 | Copyright (c) Microsoft Corporation. All rights reserved. 654 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 655 | this file except in compliance with the License. You may obtain a copy of the 656 | License at http://www.apache.org/licenses/LICENSE-2.0 657 | 658 | THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 659 | KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 660 | WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 661 | MERCHANTABLITY OR NON-INFRINGEMENT. 662 | 663 | See the Apache Version 2.0 License for specific language governing permissions 664 | and limitations under the License. 665 | ***************************************************************************** */ 666 | /* global global, define, System, Reflect, Promise */ 667 | var __extends; 668 | var __assign; 669 | var __rest; 670 | var __decorate; 671 | var __param; 672 | var __metadata; 673 | var __awaiter; 674 | var __generator; 675 | var __exportStar; 676 | var __values; 677 | var __read; 678 | var __spread; 679 | var __await; 680 | var __asyncGenerator; 681 | var __asyncDelegator; 682 | var __asyncValues; 683 | var __makeTemplateObject; 684 | var __importStar; 685 | var __importDefault; 686 | (function (factory) { 687 | var root = typeof global === "object" ? global : typeof self === "object" ? self : typeof this === "object" ? this : {}; 688 | if (typeof define === "function" && define.amd) { 689 | define("tslib", ["exports"], function (exports) { factory(createExporter(root, createExporter(exports))); }); 690 | } 691 | else if (typeof module === "object" && typeof module.exports === "object") { 692 | factory(createExporter(root, createExporter(module.exports))); 693 | } 694 | else { 695 | factory(createExporter(root)); 696 | } 697 | function createExporter(exports, previous) { 698 | if (exports !== root) { 699 | if (typeof Object.create === "function") { 700 | Object.defineProperty(exports, "__esModule", { value: true }); 701 | } 702 | else { 703 | exports.__esModule = true; 704 | } 705 | } 706 | return function (id, v) { return exports[id] = previous ? previous(id, v) : v; }; 707 | } 708 | }) 709 | (function (exporter) { 710 | var extendStatics = Object.setPrototypeOf || 711 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 712 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 713 | 714 | __extends = function (d, b) { 715 | extendStatics(d, b); 716 | function __() { this.constructor = d; } 717 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 718 | }; 719 | 720 | __assign = Object.assign || function (t) { 721 | for (var s, i = 1, n = arguments.length; i < n; i++) { 722 | s = arguments[i]; 723 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; 724 | } 725 | return t; 726 | }; 727 | 728 | __rest = function (s, e) { 729 | var t = {}; 730 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) 731 | t[p] = s[p]; 732 | if (s != null && typeof Object.getOwnPropertySymbols === "function") 733 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) 734 | t[p[i]] = s[p[i]]; 735 | return t; 736 | }; 737 | 738 | __decorate = function (decorators, target, key, desc) { 739 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 740 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 741 | else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; 742 | return c > 3 && r && Object.defineProperty(target, key, r), r; 743 | }; 744 | 745 | __param = function (paramIndex, decorator) { 746 | return function (target, key) { decorator(target, key, paramIndex); } 747 | }; 748 | 749 | __metadata = function (metadataKey, metadataValue) { 750 | if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue); 751 | }; 752 | 753 | __awaiter = function (thisArg, _arguments, P, generator) { 754 | return new (P || (P = Promise))(function (resolve, reject) { 755 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 756 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 757 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 758 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 759 | }); 760 | }; 761 | 762 | __generator = function (thisArg, body) { 763 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 764 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 765 | function verb(n) { return function (v) { return step([n, v]); }; } 766 | function step(op) { 767 | if (f) throw new TypeError("Generator is already executing."); 768 | while (_) try { 769 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 770 | if (y = 0, t) op = [op[0] & 2, t.value]; 771 | switch (op[0]) { 772 | case 0: case 1: t = op; break; 773 | case 4: _.label++; return { value: op[1], done: false }; 774 | case 5: _.label++; y = op[1]; op = [0]; continue; 775 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 776 | default: 777 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 778 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 779 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 780 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 781 | if (t[2]) _.ops.pop(); 782 | _.trys.pop(); continue; 783 | } 784 | op = body.call(thisArg, _); 785 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 786 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 787 | } 788 | }; 789 | 790 | __exportStar = function (m, exports) { 791 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; 792 | }; 793 | 794 | __values = function (o) { 795 | var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; 796 | if (m) return m.call(o); 797 | return { 798 | next: function () { 799 | if (o && i >= o.length) o = void 0; 800 | return { value: o && o[i++], done: !o }; 801 | } 802 | }; 803 | }; 804 | 805 | __read = function (o, n) { 806 | var m = typeof Symbol === "function" && o[Symbol.iterator]; 807 | if (!m) return o; 808 | var i = m.call(o), r, ar = [], e; 809 | try { 810 | while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); 811 | } 812 | catch (error) { e = { error: error }; } 813 | finally { 814 | try { 815 | if (r && !r.done && (m = i["return"])) m.call(i); 816 | } 817 | finally { if (e) throw e.error; } 818 | } 819 | return ar; 820 | }; 821 | 822 | __spread = function () { 823 | for (var ar = [], i = 0; i < arguments.length; i++) 824 | ar = ar.concat(__read(arguments[i])); 825 | return ar; 826 | }; 827 | 828 | __await = function (v) { 829 | return this instanceof __await ? (this.v = v, this) : new __await(v); 830 | }; 831 | 832 | __asyncGenerator = function (thisArg, _arguments, generator) { 833 | if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); 834 | var g = generator.apply(thisArg, _arguments || []), i, q = []; 835 | return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; 836 | function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } 837 | function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } 838 | function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } 839 | function fulfill(value) { resume("next", value); } 840 | function reject(value) { resume("throw", value); } 841 | function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } 842 | }; 843 | 844 | __asyncDelegator = function (o) { 845 | var i, p; 846 | return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; 847 | function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } 848 | }; 849 | 850 | __asyncValues = function (o) { 851 | if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); 852 | var m = o[Symbol.asyncIterator], i; 853 | return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); 854 | function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } 855 | function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } 856 | }; 857 | 858 | __makeTemplateObject = function (cooked, raw) { 859 | if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } 860 | return cooked; 861 | }; 862 | 863 | __importStar = function (mod) { 864 | if (mod && mod.__esModule) return mod; 865 | var result = {}; 866 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 867 | result["default"] = mod; 868 | return result; 869 | }; 870 | 871 | __importDefault = function (mod) { 872 | return (mod && mod.__esModule) ? mod : { "default": mod }; 873 | }; 874 | 875 | exporter("__extends", __extends); 876 | exporter("__assign", __assign); 877 | exporter("__rest", __rest); 878 | exporter("__decorate", __decorate); 879 | exporter("__param", __param); 880 | exporter("__metadata", __metadata); 881 | exporter("__awaiter", __awaiter); 882 | exporter("__generator", __generator); 883 | exporter("__exportStar", __exportStar); 884 | exporter("__values", __values); 885 | exporter("__read", __read); 886 | exporter("__spread", __spread); 887 | exporter("__await", __await); 888 | exporter("__asyncGenerator", __asyncGenerator); 889 | exporter("__asyncDelegator", __asyncDelegator); 890 | exporter("__asyncValues", __asyncValues); 891 | exporter("__makeTemplateObject", __makeTemplateObject); 892 | exporter("__importStar", __importStar); 893 | exporter("__importDefault", __importDefault); 894 | }); 895 | 896 | }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 897 | },{}],10:[function(require,module,exports){ 898 | const movies = require('./movies.json') 899 | const { Chromecast, Register, Controller } = require('..') 900 | 901 | const options = { 902 | receiverApplicationId: '87F34079', 903 | autoJoinPolicy: Chromecast.AutoJoinPolicy.ORIGIN_SCOPED 904 | } 905 | 906 | let pushIndex = 2 907 | let item = 0 908 | const videos = [] 909 | 910 | registerClick('pp', () => Controller.togglePlay()) 911 | registerClick('stop', () => disconnect()) 912 | registerClick('seek', () => Controller.seekToPercentage(.99)) 913 | registerClick('add', () => { 914 | Chromecast.appendToQueue(createEntity(movies[pushIndex++])) 915 | }) 916 | registerClick('skip', () => Chromecast.playNext()) 917 | registerClick('prev', () => Chromecast.playPrevious()) 918 | registerClick('remove', () => { 919 | const id = parseInt(document.getElementById('item').value) 920 | Chromecast.removeFromQueue(id) 921 | }) 922 | 923 | Chromecast.initializeCastService(options).catch(console.error) 924 | Chromecast.onAnyEvent(console.log) 925 | Chromecast.setReadyStateListner(onReady) 926 | Chromecast.setResumeStateListener(onResume) 927 | Chromecast.setErrorListener(console.error) 928 | 929 | function createEntity(movie) { 930 | return Chromecast.newMediaEntity(movie.url, 'video/mp4', movie.title, movie.image) 931 | } 932 | 933 | function registerClick(id, fn) { 934 | document.getElementById(id).addEventListener('click', fn) 935 | } 936 | 937 | function disconnect() { 938 | Controller && Controller.stop() 939 | Chromecast.clearQueue() 940 | Chromecast.disconnect() 941 | } 942 | 943 | function makeTempQ() { 944 | videos.push(createEntity(movies[0])) 945 | videos.push(createEntity(movies[1])) 946 | } 947 | 948 | function onResume() { 949 | makeTempQ() 950 | return { 951 | items: videos, 952 | currentItem: item 953 | } 954 | } 955 | 956 | function onReady() { 957 | makeTempQ() 958 | console.log(videos.length + ' in queue', videos) 959 | console.log(Chromecast.getCastDeviceName()) 960 | 961 | Chromecast.enqueueItems(videos) 962 | Chromecast.startQueue() 963 | 964 | Chromecast.on('queueItem', i => { 965 | item = i 966 | console.log('queueItem changed to %d', i) 967 | }) 968 | // Chromecast.on('queueRemove', i => console.log('queueRemove', i)) 969 | // Chromecast.on('queueInsert', i => console.log('queueInsert', i)) 970 | // Chromecast.on('currentTime', t => console.log('currentTime: %d',t)) 971 | //Chromecast.on('isMediaLoaded', is => console.log('isMediaLoaded', is)) 972 | 973 | 974 | } 975 | 976 | 977 | 978 | 979 | },{"..":1,"./movies.json":11}],11:[function(require,module,exports){ 980 | module.exports=[ 981 | { 982 | "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", 983 | "title": "Big Buck Bunny", 984 | "image": "https://static.edyoutoo.media/posters/7CF657CC6C319470/P-7CF657CC6C319470-540.png" 985 | }, 986 | { 987 | "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4", 988 | "title": "Elephant's Dream", 989 | "image": "https://static.edyoutoo.media/posters/7CF657CC6C319470/P-7CF657CC6C319470-540.png" 990 | }, 991 | { 992 | "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4", 993 | "title": "For Bigger Blazes", 994 | "image": "https://static.edyoutoo.media/posters/7CF657CC6C319470/P-7CF657CC6C319470-540.png" 995 | }, 996 | { 997 | "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4", 998 | "title": "For Bigger Escapes", 999 | "image": "https://static.edyoutoo.media/posters/7CF657CC6C319470/P-7CF657CC6C319470-540.png" 1000 | }, 1001 | { 1002 | "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4", 1003 | "title": "For Bigger Fun", 1004 | "image": "https://static.edyoutoo.media/posters/7CF657CC6C319470/P-7CF657CC6C319470-540.png" 1005 | }, 1006 | { 1007 | "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4", 1008 | "title": "For Bigger Joyrides", 1009 | "image": "https://static.edyoutoo.media/posters/7CF657CC6C319470/P-7CF657CC6C319470-540.png" 1010 | }, 1011 | { 1012 | "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerMeltdowns.mp4", 1013 | "title": "For Bigger Meltdowns", 1014 | "image": "https://static.edyoutoo.media/posters/7CF657CC6C319470/P-7CF657CC6C319470-540.png" 1015 | } 1016 | ] 1017 | },{}]},{},[10]); 1018 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | 2 | export declare type HandlerFn = (value: any) => void; 3 | 4 | declare namespace cast { 5 | export namespace framework { 6 | export interface CastOptions { 7 | autoJoinPolicy: chrome.cast.AutoJoinPolicy; 8 | language?: string; 9 | receiverApplicationId?: string; 10 | resumeSavedSession?: boolean; 11 | } 12 | export class RemotePlayerController { 13 | constructor(player: RemotePlayer); 14 | playOrPause(): void; 15 | stop(): void; 16 | seek(): void; 17 | muteOrUnmute(): void; 18 | setVolumeLevel(): void; 19 | getFormattedTime(timeInSec: number): string; 20 | getSeekPosition(currentTime: number, duration: number): number; 21 | getSeekTime(currentPosition: number, duration: number): number; 22 | 23 | addEventListener( 24 | type: RemotePlayerEventType, 25 | handler: (event: RemotePlayerChangedEvent) => void 26 | ): void; 27 | removeEventListener( 28 | type: RemotePlayerEventType, 29 | handler: (event: RemotePlayerChangedEvent) => void 30 | ): void; 31 | } 32 | export interface RemotePlayer { 33 | isConnected: boolean; 34 | isMediaLoaded: boolean; 35 | duration: number; 36 | currentTime: number; 37 | volumeLevel: number; 38 | canControlVolume: boolean; 39 | isPaused: boolean; 40 | isMuted: boolean; 41 | canPause: boolean; 42 | canSeek: boolean; 43 | displayName: string; 44 | statusText: string; 45 | title: string; 46 | displayStatus: string; 47 | mediaInfo?: chrome.cast.media.MediaInfo; 48 | imageUrl: string | null; 49 | playerState: chrome.cast.media.PlayerState | null; 50 | savedPlayerState: SavedPlayerState | null; 51 | controller: RemotePlayerController | null; 52 | } 53 | export interface CastSession { 54 | 55 | } 56 | interface SavedPlayerState { 57 | mediaInfo: chrome.cast.media.PlayerState | null; 58 | currentTime: number; 59 | isPaused: boolean; 60 | } 61 | class RemotePlayerChangedEvent extends EventData { 62 | constructor(type: RemotePlayerEventType, field: string, value: T); 63 | field: string; 64 | value: T; 65 | } 66 | abstract class EventData { 67 | constructor(type: string); 68 | type: string; 69 | } 70 | enum RemotePlayerEventType { 71 | ANY_CHANGE = "anyChanged", 72 | IS_CONNECTED_CHANGED = "isConnectedChanged", 73 | IS_MEDIA_LOADED_CHANGED = "isMediaLoadedChanged", 74 | DURATION_CHANGED = "durationChanged", 75 | CURRENT_TIME_CHANGED = "currentTimeChanged", 76 | IS_PAUSED_CHANGED = "isPausedChanged", 77 | VOLUME_LEVEL_CHANGED = "volumeLevelChanged", 78 | CAN_CONTROL_VOLUME_CHANGED = "canControlVolumeChanged", 79 | IS_MUTED_CHANGED = "isMutedChanged", 80 | CAN_PAUSE_CHANGED = "canPauseChanged", 81 | CAN_SEEK_CHANGED = "canSeekChanged", 82 | DISPLAY_NAME_CHANGED = "displayNameChanged", 83 | STATUS_TEXT_CHANGED = "statusTextChanged", 84 | TITLE_CHANGED = "titleChanged", 85 | DISPLAY_STATUS_CHANGED = "displayStatusChanged", 86 | MEDIA_INFO_CHANGED = "mediaInfoChanged", 87 | IMAGE_URL_CHANGED = "imageUrlChanged", 88 | PLAYER_STATE_CHANGED = "playerStateChanged", 89 | LIVE_SEEKABLE_RANGE_CHANGED = "liveSeekableRange" 90 | } 91 | } 92 | } 93 | 94 | declare type AbstractMetaData = chrome.cast.media.GenericMediaMetadata | chrome.cast.media.MovieMediaMetadata | chrome.cast.media.MusicTrackMediaMetadata | chrome.cast.media.PhotoMediaMetadata 95 | declare class Media { 96 | static setDefaultImageProperties(w: number, h: number): void; 97 | static newEntity(mediaId: string, mimeType: string, title?: string, image?: string, meta?: AbstractMetaData): chrome.cast.media.MediaInfo; 98 | } 99 | 100 | export interface Options { 101 | autoJoinPolicy?: string; 102 | receiverApplicationId?: string; 103 | language?: string; 104 | resumeSavedSession?: boolean; 105 | } 106 | 107 | export declare class CastOptions { 108 | constructor(); 109 | setOptions(options: Options): void; 110 | readonly options: cast.framework.CastOptions; 111 | } 112 | 113 | export declare class Chromecast { 114 | constructor(); 115 | /** 116 | * Whether or not the chromecase service has been 117 | * established and can accept media requests 118 | */ 119 | static isReady(): boolean; 120 | static initializeCastService(options?: Options): Promise; 121 | static setStartingStateListener(listener: () => void): void; 122 | static setReadyStateListner(listener: () => void): void; 123 | static setShutownStateListener(listener: () => void): void; 124 | static setResumeStateListener(listener: () => void): ResumeState; 125 | static setErrorListener(listener: (e: chrome.cast.Error) => void): void; 126 | static readonly controller: cast.framework.RemotePlayerController; 127 | static readonly player: cast.framework.RemotePlayer; 128 | static readonly session: cast.framework.CastSession; 129 | static readonly AutoJoinPolicy: { 130 | CUSTOM_CONTROLLER_SCOPED: string; 131 | TAB_AND_ORIGIN_SCOPED: string; 132 | ORIGIN_SCOPED: string; 133 | PAGE_SCOPED: string; 134 | }; 135 | static disconnect(): void; 136 | static on(event: EventType, fn: HandlerFn): void; 137 | static onAnyEvent(listener: (event: EventType, value: any) => void): void; 138 | static off(event: EventType): void; 139 | static unregisterAll(): void; 140 | static getCurrentMedia(): chrome.cast.media.MediaInfo | null; 141 | static newMediaEntity(mediaId: string, mimeType: string): chrome.cast.media.MediaInfo; 142 | static newMediaEntity(mediaId: string, mimeType: string, title: string): chrome.cast.media.MediaInfo; 143 | static newMediaEntity(mediaId: string, mimeType: string, title: string, image: string): chrome.cast.media.MediaInfo; 144 | static newMediaEntity(mediaId: string, mimeType: string, title?: string, image?: string, meta?: AbstractMetaData): chrome.cast.media.MediaInfo; 145 | static enqueue(item: chrome.cast.media.MediaInfo): void; 146 | static enqueueItems(items: chrome.cast.media.MediaInfo[]): void; 147 | static appendToQueue(item: chrome.cast.media.MediaInfo): void; 148 | static removeFromQueue(item: number): void; 149 | static reorderQueue(from: number, to: number): void 150 | static clearQueue(): void; 151 | static startQueue(): void; 152 | static startQueue(startTime: number): void; 153 | static playNext(): void; 154 | static playPrevious(): void; 155 | static getCastDeviceName(): string 156 | } 157 | 158 | export declare class Controller { 159 | static togglePlay(): void; 160 | static toggleMute(): void; 161 | static seek(seconds: number): void; 162 | static seekToTime(seconds: number): void; 163 | static seekToPercentage(ratio: number): void; 164 | static stop(): void; 165 | static stop(drainQueue: boolean): void; 166 | static adjustVolume(volume: number): void; 167 | static rewind(): void; 168 | } 169 | 170 | export declare interface ResumeState { 171 | items: chrome.cast.media.MediaInfo[] 172 | currentItem: number 173 | } 174 | 175 | 176 | export type EventType = 'isConnected' | 'isMediaLoaded' | 'duration' 177 | | 'currentTime' | 'isPaused' | 'volumeLevel' | 'canControlVolume' 178 | | 'isMuted' | 'canPause' | 'canSeek' | 'displayName' | 'statusText' 179 | | 'title' | 'displayStatus' | 'imageUrl' 180 | | 'mediaInfo' | 'playerState' | & QueueEventType 181 | export declare type QueueEventType = 'queueLoad' | 'queueComplete' | 'queueInsert' | 'queueRemove' | 'queueItem' | 'queueUpdate' 182 | 183 | 184 | // Type definitions for Chrome Cast application development 185 | // Project: https://developers.google.com/cast/ 186 | // Definitions by: Thomas Stig Jacobsen 187 | // Stefan Ullinger 188 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 189 | 190 | //////////////////// 191 | // Cast 192 | // @see https://code.google.com/p/chromium/codesearch#chromium/src/ui/file_manager/externs/chrome_cast.js 193 | //////////////////// 194 | declare namespace chrome.cast { 195 | /** 196 | * @enum {string} 197 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast#.AutoJoinPolicy 198 | */ 199 | export enum AutoJoinPolicy { 200 | CUSTOM_CONTROLLER_SCOPED = "custom_controller_scoped", 201 | TAB_AND_ORIGIN_SCOPED = "tab_and_origin_scoped", 202 | ORIGIN_SCOPED = "origin_scoped", 203 | PAGE_SCOPED = "page_scoped" 204 | } 205 | 206 | /** 207 | * @enum {string} 208 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast#.DefaultActionPolicy 209 | */ 210 | export enum DefaultActionPolicy { 211 | CREATE_SESSION = "create_session", 212 | CAST_THIS_TAB = "cast_this_tab" 213 | } 214 | 215 | /** 216 | * @enum {string} 217 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast#.Capability 218 | */ 219 | export enum Capability { 220 | VIDEO_OUT = "video_out", 221 | AUDIO_OUT = "audio_out", 222 | VIDEO_IN = "video_in", 223 | AUDIO_IN = "audio_in", 224 | MULTIZONE_GROUP = "multizone_group" 225 | } 226 | 227 | /** 228 | * @enum {string} 229 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast#.ErrorCode 230 | */ 231 | export enum ErrorCode { 232 | CANCEL = "cancel", 233 | TIMEOUT = "timeout", 234 | API_NOT_INITIALIZED = "api_not_initialized", 235 | INVALID_PARAMETER = "invalid_parameter", 236 | EXTENSION_NOT_COMPATIBLE = "extension_not_compatible", 237 | EXTENSION_MISSING = "extension_missing", 238 | RECEIVER_UNAVAILABLE = "receiver_unavailable", 239 | SESSION_ERROR = "session_error", 240 | CHANNEL_ERROR = "channel_error", 241 | LOAD_MEDIA_FAILED = "load_media_failed" 242 | } 243 | 244 | /** 245 | * @enum {string} 246 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast#.ReceiverAvailability 247 | */ 248 | export enum ReceiverAvailability { 249 | AVAILABLE = "available", 250 | UNAVAILABLE = "unavailable" 251 | } 252 | 253 | /** 254 | * @enum {string} 255 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast#.SenderPlatform 256 | */ 257 | export enum SenderPlatform { 258 | CHROME = "chrome", 259 | IOS = "ios", 260 | ANDROID = "android" 261 | } 262 | 263 | /** 264 | * @enum {string} 265 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast#.ReceiverType 266 | */ 267 | export enum ReceiverType { 268 | CAST = "cast", 269 | DIAL = "dial", 270 | HANGOUT = "hangout", 271 | CUSTOM = "custom" 272 | } 273 | 274 | /** 275 | * @enum {string} 276 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast#.ReceiverAction 277 | */ 278 | export enum ReceiverAction { 279 | CAST = "cast", 280 | STOP = "stop" 281 | } 282 | 283 | 284 | 285 | /** 286 | * @enum {string} 287 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast#.SessionStatus 288 | */ 289 | export enum SessionStatus { 290 | CONNECTED = "connected", 291 | DISCONNECTED = "disconnected", 292 | STOPPED = "stopped" 293 | } 294 | 295 | /** 296 | * @const {!Array} 297 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast#.VERSION 298 | */ 299 | export var VERSION: Array; 300 | 301 | /** 302 | * @type {boolean} 303 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast#.isAvailable 304 | */ 305 | export var isAvailable: boolean; 306 | 307 | /** 308 | * @param {!chrome.cast.ApiConfig} apiConfig 309 | * @param {function()} successCallback 310 | * @param {function(chrome.cast.Error)} errorCallback 311 | */ 312 | export function initialize( 313 | apiConfig: chrome.cast.ApiConfig, 314 | successCallback: Function, 315 | errorCallback: (error: chrome.cast.Error) => void 316 | ): void; 317 | 318 | /** 319 | * @param {function(!chrome.cast.Session)} successCallback 320 | * @param {function(chrome.cast.Error)} errorCallback 321 | * @param {chrome.cast.SessionRequest=} opt_sessionRequest 322 | * @param {string=} opt_label 323 | */ 324 | export function requestSession( 325 | successCallback: (session: chrome.cast.Session) => void, 326 | errorCallback: (error: chrome.cast.Error) => void, 327 | sessionRequest?: chrome.cast.SessionRequest, 328 | label?: string 329 | ): void 330 | 331 | /** 332 | * @param {string} sessionId The id of the session to join. 333 | */ 334 | export function requestSessionById( 335 | sessionId: string 336 | ): void 337 | 338 | /** 339 | * @param {chrome.cast.ReceiverActionListener} listener 340 | */ 341 | export function addReceiverActionListener( 342 | listener: (receiver: chrome.cast.Receiver, receiverAction: chrome.cast.ReceiverAction) => void 343 | ): void 344 | 345 | /** 346 | * @param {chrome.cast.ReceiverActionListener} listener 347 | */ 348 | export function removeReceiverActionListener( 349 | listener: (receiver: chrome.cast.Receiver, receiverAction: chrome.cast.ReceiverAction) => void 350 | ): void 351 | 352 | /** 353 | * @param {string} message The message to log. 354 | */ 355 | export function logMessage( 356 | message: string 357 | ): void 358 | 359 | /** 360 | * @param {!Array} receivers 361 | * @param {function()} successCallback 362 | * @param {function(chrome.cast.Error)} errorCallback 363 | */ 364 | export function setCustomReceivers( 365 | receivers: Array, 366 | successCallback: Function, 367 | errorCallback: (error: chrome.cast.Error) => void 368 | ): void 369 | 370 | /** 371 | * @param {!chrome.cast.Receiver} receiver 372 | * @param {function()} successCallback 373 | * @param {function(chrome.cast.Error)} errorCallback 374 | */ 375 | export function setReceiverDisplayStatus( 376 | receiver: chrome.cast.Receiver, 377 | successCallback: Function, 378 | errorCallback: (error: chrome.cast.Error) => void 379 | ): void 380 | 381 | /** 382 | * @param {string} escaped A string to unescape. 383 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast#.unescape 384 | */ 385 | export function unescape( 386 | escaped: string 387 | ): string 388 | 389 | export class ApiConfig { 390 | /** 391 | * @param {!chrome.cast.SessionRequest} sessionRequest 392 | * @param {function(!chrome.cast.Session)} sessionListener 393 | * @param {function(!chrome.cast.ReceiverAvailability,Array)} 394 | * receiverListener 395 | * @param {chrome.cast.AutoJoinPolicy=} opt_autoJoinPolicy 396 | * @param {chrome.cast.DefaultActionPolicy=} opt_defaultActionPolicy 397 | * @constructor 398 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.ApiConfig 399 | */ 400 | constructor( 401 | sessionRequest: chrome.cast.SessionRequest, 402 | sessionListener: (session: chrome.cast.Session) => void, 403 | receiverListener: (receiverAvailability: chrome.cast.ReceiverAvailability) => void, 404 | autoJoinPolicy?: chrome.cast.AutoJoinPolicy, 405 | defaultActionPolicy?: chrome.cast.DefaultActionPolicy 406 | ); 407 | 408 | sessionRequest: chrome.cast.SessionRequest; 409 | sessionListener: (session: chrome.cast.Session) => void; 410 | receiverListener: (receiverAvailability: chrome.cast.ReceiverAvailability) => void; 411 | autoJoinPolicy?: chrome.cast.AutoJoinPolicy; 412 | defaultActionPolicy: chrome.cast.DefaultActionPolicy; 413 | } 414 | 415 | export class Error { 416 | /** 417 | * @param {!chrome.cast.ErrorCode} code 418 | * @param {string=} opt_description 419 | * @param {Object=} opt_details 420 | * @constructor 421 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Error 422 | */ 423 | constructor( 424 | code: chrome.cast.ErrorCode, 425 | description?: string, 426 | details?: Object 427 | ); 428 | 429 | code: chrome.cast.ErrorCode; 430 | description?: string; 431 | details?: string; 432 | 433 | } 434 | 435 | export class Image { 436 | /** 437 | * @param {string} url 438 | * @constructor 439 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Image 440 | */ 441 | constructor(url: string); 442 | 443 | url: string; 444 | height?: number; 445 | width?: number; 446 | } 447 | 448 | export class SenderApplication { 449 | /** 450 | * @param {!chrome.cast.SenderPlatform} platform 451 | * @constructor 452 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.SenderApplication 453 | */ 454 | constructor( 455 | platform: chrome.cast.SenderPlatform 456 | ); 457 | 458 | platform: chrome.cast.SenderPlatform; 459 | url?: string; 460 | packageId?: string; 461 | } 462 | 463 | export class SessionRequest { 464 | /** 465 | * @param {string} appId 466 | * @param {!Array=} opt_capabilities 467 | * @param {number=} opt_timeout 468 | * @constructor 469 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.SessionRequest 470 | */ 471 | constructor( 472 | appId: string, 473 | capabilities?: Array, 474 | timeout?: number 475 | ); 476 | 477 | appId: string; 478 | capabilities: Array; 479 | requestSessionTimeout: number; 480 | language?: string; 481 | } 482 | 483 | export class Session { 484 | /** 485 | * @param {string} sessionId 486 | * @param {string} appId 487 | * @param {string} displayName 488 | * @param {!Array} appImages 489 | * @param {!chrome.cast.Receiver} receiver 490 | * @constructor 491 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session 492 | */ 493 | constructor( 494 | sessionId: string, 495 | appId: string, 496 | displayName: string, 497 | appImages: Array, 498 | receiver: chrome.cast.Receiver 499 | ); 500 | 501 | sessionId: string; 502 | appId: string; 503 | displayName: string; 504 | appImages: Array; 505 | receiver: chrome.cast.Receiver; 506 | senderApps: Array; 507 | namespaces: Array<{ name: string }>; 508 | media: Array; 509 | status: chrome.cast.SessionStatus 510 | 511 | /** 512 | * @param {number} newLevel 513 | * @param {function()} successCallback 514 | * @param {function(chrome.cast.Error)} errorCallback 515 | */ 516 | setReceiverVolumeLevel( 517 | newLevel: number, 518 | successCallback: Function, 519 | errorCallback: (error: chrome.cast.Error) => void 520 | ): void 521 | 522 | /** 523 | * @param {boolean} muted 524 | * @param {function()} successCallback 525 | * @param {function(chrome.cast.Error)} errorCallback 526 | */ 527 | setReceiverMuted( 528 | muted: boolean, 529 | successCallback: Function, 530 | errorCallback: (error: chrome.cast.Error) => void 531 | ): void 532 | 533 | /** 534 | * @param {function()} successCallback 535 | * @param {function(chrome.cast.Error)} errorCallback 536 | */ 537 | leave( 538 | successCallback: Function, 539 | errorCallback: (error: chrome.cast.Error) => void 540 | ): void 541 | 542 | /** 543 | * @param {function()} successCallback 544 | * @param {function(chrome.cast.Error)} errorCallback 545 | */ 546 | stop( 547 | successCallback: Function, 548 | errorCallback: (error: chrome.cast.Error) => void 549 | ): void 550 | 551 | /** 552 | * @param {string} namespace 553 | * @param {!Object|string} message 554 | * @param {!function()} successCallback 555 | * @param {function(!chrome.cast.Error)} errorCallback 556 | */ 557 | sendMessage( 558 | namespace: string, 559 | message: string, 560 | successCallback: Function, 561 | errorCallback: (error: chrome.cast.Error) => void 562 | ): void 563 | 564 | /** 565 | * @param {function(boolean)} listener 566 | */ 567 | addUpdateListener( 568 | listener: (isAlive: boolean) => void 569 | ): void 570 | 571 | /** 572 | * @param {function(boolean)} listener 573 | */ 574 | removeUpdateListener( 575 | listener: (isAlive: boolean) => void 576 | ): void 577 | 578 | /** 579 | * @param {string} namespace 580 | * @param {function(string,string)} listener 581 | */ 582 | addMessageListener( 583 | namespace: string, 584 | listener: (namespace: string, message: string) => void 585 | ): void 586 | 587 | /** 588 | * @param {string} namespace 589 | * @param {function(string,string)} listener 590 | */ 591 | removeMessageListener( 592 | namespace: string, 593 | listener: (namespace: string, message: string) => void 594 | ): void 595 | 596 | /** 597 | * @param {function(!chrome.cast.media.Media)} listener 598 | */ 599 | addMediaListener( 600 | listener: (media: chrome.cast.media.Media) => void 601 | ): void 602 | 603 | /** 604 | * @param {function(!chrome.cast.media.Media)} listener 605 | */ 606 | removeMediaListener( 607 | listener: (media: chrome.cast.media.Media) => void 608 | ): void 609 | 610 | /** 611 | * @param {!chrome.cast.media.LoadRequest} loadRequest 612 | * @param {function(!chrome.cast.media.Media)} successCallback 613 | * @param {function(!chrome.cast.Error)} errorCallback 614 | */ 615 | loadMedia( 616 | loadRequest: chrome.cast.media.LoadRequest, 617 | successCallback: (media: chrome.cast.media.Media) => void, 618 | errorCallback: (error: chrome.cast.Error) => void 619 | ): void 620 | 621 | /** 622 | * @param {!chrome.cast.media.QueueLoadRequest} queueLoadRequest 623 | * @param {function(!chrome.cast.media.Media)} successCallback 624 | * @param {function(!chrome.cast.Error)} errorCallback 625 | */ 626 | queueLoad( 627 | queueLoadRequest: chrome.cast.media.QueueLoadRequest, 628 | successCallback: (media: chrome.cast.media.Media) => void, 629 | errorCallback: (error: chrome.cast.Error) => void 630 | ): void 631 | } 632 | 633 | export class Receiver { 634 | /** 635 | * @param {string} label 636 | * @param {string} friendlyName 637 | * @param {Array=} opt_capabilities 638 | * @param {chrome.cast.Volume=} opt_volume 639 | * @constructor 640 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Receiver 641 | */ 642 | constructor( 643 | label: string, 644 | friendlyName: string, 645 | capabilities?: Array, 646 | volume?: chrome.cast.Volume 647 | ); 648 | 649 | label: string; 650 | friendlyName: string; 651 | capabilities: Array; 652 | volume: chrome.cast.Volume; 653 | receiverType: chrome.cast.ReceiverType; 654 | displayStatus: chrome.cast.ReceiverDisplayStatus; 655 | } 656 | 657 | export class ReceiverDisplayStatus { 658 | /** 659 | * @param {string} statusText 660 | * @param {!Array} appImages 661 | * @constructor 662 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.ReceiverDisplayStatus 663 | */ 664 | constructor( 665 | statusText: string, 666 | appImages: Array 667 | ); 668 | 669 | statusText: string; 670 | appImages: Array; 671 | } 672 | 673 | export class Volume { 674 | /** 675 | * @param {?number=} opt_level 676 | * @param {?boolean=} opt_muted 677 | * @constructor 678 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Volume 679 | */ 680 | constructor( 681 | level?: number, 682 | muted?: boolean 683 | ); 684 | 685 | level?: number; 686 | muted?: boolean; 687 | } 688 | } 689 | 690 | declare namespace chrome.cast.media { 691 | 692 | export var DEFAULT_MEDIA_RECEIVER_APP_ID: string; 693 | 694 | /** 695 | * @enum {string} 696 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media#.MediaCommand 697 | */ 698 | export enum MediaCommand { 699 | PAUSE = "pause", 700 | SEEK = "seek", 701 | STREAM_VOLUME = "stream_volume", 702 | STREAM_MUTE = "stream_mute" 703 | } 704 | 705 | /** 706 | * @enum {number} 707 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media#.MetadataType 708 | */ 709 | export enum MetadataType { 710 | GENERIC, 711 | TV_SHOW, 712 | MOVIE, 713 | MUSIC_TRACK, 714 | PHOTO 715 | } 716 | 717 | /** 718 | * @enum {string} 719 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media#.PlayerState 720 | */ 721 | export enum PlayerState { 722 | IDLE = "IDLE", 723 | PLAYING = "PLAYING", 724 | PAUSED = "PAUSED", 725 | BUFFERING = "BUFFERING" 726 | } 727 | 728 | /** 729 | * @enum {string} 730 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media#.ResumeState 731 | */ 732 | export enum ResumeState { 733 | PLAYBACK_START = "PLAYBACK_START", 734 | PLAYBACK_PAUSE = "PLAYBACK_PAUSE" 735 | } 736 | 737 | /** 738 | * @enum {string} 739 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media#.StreamType 740 | */ 741 | export enum StreamType { 742 | BUFFERED = "BUFFERED", 743 | LIVE = "LIVE", 744 | OTHER = "OTHER" 745 | } 746 | 747 | /** 748 | * @enum {string} 749 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media#.IdleReason 750 | */ 751 | export enum IdleReason { 752 | CANCELLED = "CANCELLED", 753 | INTERRUPTED = "INTERRUPTED", 754 | FINISHED = "FINISHED", 755 | ERROR = "ERROR" 756 | } 757 | 758 | /** 759 | * @enum {string} 760 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media#.RepeatMode 761 | */ 762 | export enum RepeatMode { 763 | OFF = "REPEAT_OFF", 764 | ALL = "REPEAT_ALL", 765 | SINGLE = "REPEAT_SINGLE", 766 | ALL_AND_SHUFFLE = "REPEAT_ALL_AND_SHUFFLE" 767 | } 768 | 769 | export class QueueItem { 770 | /** 771 | * @param {!chrome.cast.media.MediaInfo} mediaInfo 772 | * @constructor 773 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.QueueItem 774 | */ 775 | constructor( 776 | mediaInfo: chrome.cast.media.MediaInfo 777 | ); 778 | 779 | activeTrackIds: Array; 780 | autoplay: boolean; 781 | customData: Object; 782 | itemId: number; 783 | media: chrome.cast.media.MediaInfo; 784 | preloadTime: number; 785 | startTime: number; 786 | } 787 | 788 | export class QueueLoadRequest { 789 | /** 790 | * @param {!Array} items 791 | * @constructor 792 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.QueueLoadRequest 793 | */ 794 | constructor( 795 | items: Array 796 | ); 797 | 798 | customData: Object; 799 | items: Array; 800 | repeatMode: chrome.cast.media.RepeatMode; 801 | startIndex: number; 802 | } 803 | 804 | export class QueueInsertItemsRequest { 805 | /** 806 | * @param {!Array} 807 | * @constructor 808 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.QueueInsertItemsRequest 809 | */ 810 | constructor( 811 | itemsToInsert: Array 812 | ); 813 | 814 | customData: Object; 815 | insertBefore: number; 816 | items: Array; 817 | } 818 | 819 | export class QueueRemoveItemsRequest { 820 | /** 821 | * @param {!Array} 822 | * @constructor 823 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.QueueRemoveItemsRequest 824 | */ 825 | constructor( 826 | itemIdsToRemove: Array 827 | ); 828 | 829 | customData: Object; 830 | itemIds: Array; 831 | } 832 | 833 | export class QueueReorderItemsRequest { 834 | /** 835 | * @param {!Array} 836 | * @constructor 837 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.QueueReorderItemsRequest 838 | */ 839 | constructor( 840 | itemIdsToReorder: Array 841 | ); 842 | 843 | customData: Object; 844 | insertBefore: number; 845 | itemIds: Array; 846 | } 847 | 848 | export class QueueUpdateItemsRequest { 849 | /** 850 | * @param {!Array} 851 | * @constructor 852 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.QueueUpdateItemsRequest 853 | */ 854 | constructor( 855 | itemsToUpdate: Array 856 | ); 857 | 858 | customData: Object; 859 | item: Array; 860 | } 861 | 862 | /** 863 | * @enum {string} 864 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media#.TrackType 865 | */ 866 | export enum TrackType { 867 | TEXT = "TEXT", 868 | AUDIO = "AUDIO", 869 | VIDEO = "VIDEO" 870 | } 871 | 872 | /** 873 | * @enum {string} 874 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media#.TextTrackType 875 | */ 876 | export enum TextTrackType { 877 | SUBTITLES = "SUBTITLES", 878 | CAPTIONS = "CAPTIONS", 879 | DESCRIPTIONS = "DESCRIPTIONS", 880 | CHAPTERS = "CHAPTERS", 881 | METADATA = "METADATA" 882 | } 883 | 884 | /** 885 | * @enum {string} 886 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media#.TextTrackEdgeType 887 | */ 888 | export enum TextTrackEdgeType { 889 | NONE = "NONE", 890 | OUTLINE = "OUTLINE", 891 | DROP_SHADOW = "DROP_SHADOW", 892 | RAISED = "RAISED", 893 | DEPRESSED = "DEPRESSED" 894 | } 895 | 896 | /** 897 | * @enum {string} 898 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media#.TextTrackWindowType 899 | */ 900 | export enum TextTrackWindowType { 901 | NONE = "NONE", 902 | NORMAL = "NORMAL", 903 | ROUNDED_CORNERS = "ROUNDED_CORNERS" 904 | } 905 | 906 | /** 907 | * @enum {string} 908 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media#.TextTrackFontGenericFamily 909 | */ 910 | export enum TextTrackFontGenericFamily { 911 | SANS_SERIF = "SANS_SERIF", 912 | MONOSPACED_SANS_SERIF = "MONOSPACED_SANS_SERIF", 913 | SERIF = "SERIF", 914 | MONOSPACED_SERIF = "MONOSPACED_SERIF", 915 | CASUAL = "CASUAL", 916 | CURSIVE = "CURSIVE", 917 | SMALL_CAPITALS = "SMALL_CAPITALS" 918 | } 919 | 920 | /** 921 | * @enum {string} 922 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media#.TextTrackFontStyle 923 | */ 924 | export enum TextTrackFontStyle { 925 | NORMAL = "NORMAL", 926 | BOLD = "BOLD", 927 | BOLD_ITALIC = "BOLD_ITALIC", 928 | ITALIC = "ITALIC" 929 | } 930 | 931 | export class GetStatusRequest { 932 | /** 933 | * @constructor 934 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.GetStatusRequest 935 | */ 936 | constructor(); 937 | 938 | customData: Object; 939 | } 940 | 941 | export class PauseRequest { 942 | /** 943 | * @constructor 944 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.PauseRequest 945 | */ 946 | constructor(); 947 | 948 | customData: Object; 949 | } 950 | 951 | export class PlayRequest { 952 | /** 953 | * @constructor 954 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.PlayRequest 955 | */ 956 | constructor(); 957 | 958 | customData: Object; 959 | } 960 | 961 | export class SeekRequest { 962 | /** 963 | * @constructor 964 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.SeekRequest 965 | */ 966 | constructor(); 967 | 968 | currentTime: number; 969 | resumeState: chrome.cast.media.ResumeState; 970 | customData: Object; 971 | } 972 | 973 | export class StopRequest { 974 | /** 975 | * @constructor 976 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.StopRequest 977 | */ 978 | constructor(); 979 | 980 | customData: Object; 981 | } 982 | 983 | export class VolumeRequest { 984 | /** 985 | * @param {!chrome.cast.Volume} volume 986 | * @constructor 987 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.VolumeRequest 988 | */ 989 | constructor( 990 | volume: chrome.cast.Volume 991 | ); 992 | 993 | volume: chrome.cast.Volume; 994 | customData: Object; 995 | } 996 | 997 | export class LoadRequest { 998 | /** 999 | * @param {!chrome.cast.media.MediaInfo} mediaInfo 1000 | * @constructor 1001 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.LoadRequest 1002 | */ 1003 | constructor( 1004 | mediaInfo: chrome.cast.media.MediaInfo 1005 | ); 1006 | 1007 | activeTrackIds: Array; 1008 | autoplay: boolean; 1009 | currentTime: number; 1010 | customData: Object; 1011 | media: chrome.cast.media.MediaInfo; 1012 | } 1013 | 1014 | export class EditTracksInfoRequest { 1015 | /** 1016 | * @param {Array=} opt_activeTrackIds 1017 | * @param {chrome.cast.media.TextTrackStyle=} opt_textTrackStyle 1018 | * @constructor 1019 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.EditTracksInfoRequest 1020 | */ 1021 | constructor( 1022 | activeTrackIds?: Array, 1023 | textTrackStyle?: chrome.cast.media.TextTrackStyle 1024 | ); 1025 | 1026 | activeTrackIds: Array; 1027 | textTrackStyle: chrome.cast.media.TextTrackStyle; 1028 | } 1029 | 1030 | export class GenericMediaMetadata { 1031 | images: Array; 1032 | metadataType: chrome.cast.media.MetadataType; 1033 | releaseDate: string; 1034 | /** @deprecated. Use releaseDate instead. */ 1035 | releaseYear: number; 1036 | subtitle: string; 1037 | title: string; 1038 | /** @deprecated. Use metadataType instead. */ 1039 | type: chrome.cast.media.MetadataType; 1040 | } 1041 | 1042 | export class MovieMediaMetadata { 1043 | /** 1044 | * @constructor 1045 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.MovieMediaMetadata 1046 | */ 1047 | constructor(); 1048 | 1049 | images: Array; 1050 | metadataType: chrome.cast.media.MetadataType; 1051 | releaseDate: string; 1052 | /** @deprecated. Use releaseDate instead. */ 1053 | releaseYear: number; 1054 | subtitle: string; 1055 | title: string; 1056 | studio: string; 1057 | /** @deprecated. Use metadataType instead. */ 1058 | type: chrome.cast.media.MetadataType; 1059 | } 1060 | 1061 | export class TvShowMediaMetadata { 1062 | /** 1063 | * @constructor 1064 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.TvShowMediaMetadata 1065 | */ 1066 | constructor(); 1067 | 1068 | metadataType: chrome.cast.media.MetadataType; 1069 | seriesTitle: string; 1070 | title: string; 1071 | season: number; 1072 | episode: number; 1073 | images: Array; 1074 | originalAirdate: string; 1075 | 1076 | /** @deprecated. Use metadataType instead. */ 1077 | type: chrome.cast.media.MetadataType; 1078 | /** @deprecated. Use title instead. */ 1079 | episodeTitle: string; 1080 | /** @deprecated. Use season instead. */ 1081 | seasonNumber: number; 1082 | /** @deprecated. Use episode instead. */ 1083 | episodeNumber: number; 1084 | /** @deprecated. Use originalAirdate instead. */ 1085 | releaseYear: number; 1086 | } 1087 | 1088 | export class MusicTrackMediaMetadata { 1089 | /** 1090 | * @constructor 1091 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.MusicTrackMediaMetadata 1092 | */ 1093 | constructor(); 1094 | 1095 | metadataType: chrome.cast.media.MetadataType; 1096 | albumName: string; 1097 | title: string; 1098 | albumArtist: string; 1099 | artist: string; 1100 | composer: string; 1101 | songName: string; 1102 | trackNumber: number; 1103 | discNumber: number; 1104 | images: Array; 1105 | releaseDate: string; 1106 | 1107 | /** @deprecated. Use metadataType instead. */ 1108 | type: chrome.cast.media.MetadataType; 1109 | /** @deprecated. Use artist instead. */ 1110 | artistName: string; 1111 | /** @deprecated. Use releaseDate instead. */ 1112 | releaseYear: number; 1113 | } 1114 | 1115 | export class PhotoMediaMetadata { 1116 | /** 1117 | * @constructor 1118 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.PhotoMediaMetadata 1119 | */ 1120 | constructor(); 1121 | 1122 | metadataType: chrome.cast.media.MetadataType; 1123 | title: string; 1124 | artist: string; 1125 | location: string; 1126 | images: Array; 1127 | latitude: number; 1128 | longitude: number; 1129 | width: number; 1130 | height: number; 1131 | creationDateTime: string; 1132 | 1133 | /** @deprecated. Use metadataType instead. */ 1134 | type: chrome.cast.media.MetadataType; 1135 | } 1136 | 1137 | export class MediaInfo { 1138 | /** 1139 | * @param {string} contentId 1140 | * @param {string} contentType 1141 | * @constructor 1142 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.MediaInfo 1143 | */ 1144 | constructor( 1145 | contentId: string, 1146 | contentType: string 1147 | ); 1148 | 1149 | contentId: string; 1150 | streamType: chrome.cast.media.StreamType; 1151 | contentType: string; 1152 | metadata: Object; 1153 | duration: number; 1154 | tracks: Array; 1155 | textTrackStyle: chrome.cast.media.TextTrackStyle; 1156 | customData: Object; 1157 | } 1158 | 1159 | export class Media { 1160 | /** 1161 | * @param {string} sessionId 1162 | * @param {number} mediaSessionId 1163 | * @constructor 1164 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media 1165 | */ 1166 | constructor( 1167 | sessionId: string, 1168 | mediaSessionId: number 1169 | ); 1170 | 1171 | activeTrackIds: Array; 1172 | currentItemId: number; 1173 | customData: Object; 1174 | idleReason: chrome.cast.media.IdleReason; 1175 | items: Array; 1176 | loadingItemId: number; 1177 | media: chrome.cast.media.MediaInfo; 1178 | mediaSessionId: number; 1179 | playbackRate: number; 1180 | playerState: chrome.cast.media.PlayerState; 1181 | preloadedItemId: number; 1182 | repeatMode: chrome.cast.media.RepeatMode; 1183 | sessionId: string; 1184 | supportedMediaCommands: Array; 1185 | volume: chrome.cast.Volume; 1186 | 1187 | /** @deprecated. Use getEstimatedTime instead */ 1188 | currentTime: number; 1189 | 1190 | /** 1191 | * @param {chrome.cast.media.GetStatusRequest} getStatusRequest 1192 | * @param {function()} successCallback 1193 | * @param {function(!chrome.cast.Error)} errorCallback 1194 | */ 1195 | getStatus( 1196 | getStatusRequest: chrome.cast.media.GetStatusRequest, 1197 | successCallback: Function, 1198 | errorCallback: (error: chrome.cast.Error) => void 1199 | ): void 1200 | 1201 | /** 1202 | * @param {chrome.cast.media.PlayRequest} playRequest 1203 | * @param {function()} successCallback 1204 | * @param {function(!chrome.cast.Error)} errorCallback 1205 | */ 1206 | play( 1207 | playRequest: chrome.cast.media.PlayRequest, 1208 | successCallback: Function, 1209 | errorCallback: (error: chrome.cast.Error) => void 1210 | ): void 1211 | 1212 | /** 1213 | * @param {chrome.cast.media.PauseRequest} pauseRequest 1214 | * @param {function()} successCallback 1215 | * @param {function(!chrome.cast.Error)} errorCallback 1216 | */ 1217 | pause( 1218 | pauseRequest: chrome.cast.media.PauseRequest, 1219 | successCallback: Function, 1220 | errorCallback: (error: chrome.cast.Error) => void 1221 | ): void 1222 | 1223 | /** 1224 | * @param {!chrome.cast.media.SeekRequest} seekRequest 1225 | * @param {function()} successCallback 1226 | * @param {function(!chrome.cast.Error)} errorCallback 1227 | */ 1228 | seek( 1229 | seekRequest: chrome.cast.media.SeekRequest, 1230 | successCallback: Function, 1231 | errorCallback: (error: chrome.cast.Error) => void 1232 | ): void 1233 | 1234 | /** 1235 | * @param {chrome.cast.media.StopRequest} stopRequest 1236 | * @param {function()} successCallback 1237 | * @param {function(!chrome.cast.Error)} errorCallback 1238 | */ 1239 | stop( 1240 | stopRequest: chrome.cast.media.StopRequest, 1241 | successCallback: Function, 1242 | errorCallback: (error: chrome.cast.Error) => void 1243 | ): void 1244 | 1245 | /** 1246 | * @param {!chrome.cast.media.VolumeRequest} volumeRequest 1247 | * @param {function()} successCallback 1248 | * @param {function(!chrome.cast.Error)} errorCallback 1249 | */ 1250 | setVolume( 1251 | volumeRequest: chrome.cast.media.VolumeRequest, 1252 | successCallback: Function, 1253 | errorCallback: (error: chrome.cast.Error) => void 1254 | ): void 1255 | 1256 | /** 1257 | * @param {!chrome.cast.media.EditTracksInfoRequest} editTracksInfoRequest 1258 | * @param {function()} successCallback 1259 | * @param {function(!chrome.cast.Error)} errorCallback 1260 | */ 1261 | editTracksInfo( 1262 | editTracksInfoRequest: chrome.cast.media.EditTracksInfoRequest, 1263 | successCallback: Function, 1264 | errorCallback: (error: chrome.cast.Error) => void 1265 | ): void 1266 | 1267 | /** 1268 | * @param {!chrome.cast.media.MediaCommand} command 1269 | * @return {boolean} 1270 | */ 1271 | supportsCommand( 1272 | command: chrome.cast.media.MediaCommand 1273 | ): boolean 1274 | 1275 | /** 1276 | * @param {function(boolean)} listener 1277 | */ 1278 | addUpdateListener( 1279 | listener: (isAlive: boolean) => void 1280 | ): void 1281 | 1282 | /** 1283 | * @param {function(boolean)} listener 1284 | */ 1285 | removeUpdateListener( 1286 | listener: (isAlive: boolean) => void 1287 | ): void 1288 | 1289 | /** 1290 | * @return {number} 1291 | * @suppress {deprecated} Uses currentTime member to compute estimated time. 1292 | */ 1293 | getEstimatedTime(): number 1294 | 1295 | /** 1296 | * @param {!chrome.cast.media.QueueItem} item 1297 | * @param {function()} successCallback 1298 | * @param {function(!chrome.cast.Error)} errorCallback 1299 | */ 1300 | queueAppendItem( 1301 | item: chrome.cast.media.QueueItem, 1302 | successCallback: Function, 1303 | errorCallback: (error: chrome.cast.Error) => void 1304 | ): void 1305 | 1306 | /** 1307 | * @param {!chrome.cast.media.QueueInsertItemsRequest} queueInsertItemsRequest 1308 | * @param {function()} successCallback 1309 | * @param {function(!chrome.cast.Error)} errorCallback 1310 | */ 1311 | queueInsertItems( 1312 | queueInsertItemsRequest: chrome.cast.media.QueueInsertItemsRequest, 1313 | successCallback: Function, 1314 | errorCallback: (error: chrome.cast.Error) => void 1315 | ): void 1316 | 1317 | /** 1318 | * @param {!number} itemId 1319 | * @param {function()} successCallback 1320 | * @param {function(!chrome.cast.Error)} errorCallback 1321 | */ 1322 | queueJumpToItem( 1323 | itemId: number, 1324 | successCallback: Function, 1325 | errorCallback: (error: chrome.cast.Error) => void 1326 | ): void 1327 | 1328 | /** 1329 | * @param {!number} itemId 1330 | * @param {!number} newIndex 1331 | * @param {function()} successCallback 1332 | * @param {function(!chrome.cast.Error)} errorCallback 1333 | */ 1334 | queueMoveItemToNewIndex( 1335 | itemId: number, 1336 | newIndex: number, 1337 | successCallback: Function, 1338 | errorCallback: (error: chrome.cast.Error) => void 1339 | ): void 1340 | 1341 | /** 1342 | * @param {function()} successCallback 1343 | * @param {function(!chrome.cast.Error)} errorCallback 1344 | */ 1345 | queueNext( 1346 | successCallback: Function, 1347 | errorCallback: (error: chrome.cast.Error) => void 1348 | ): void 1349 | 1350 | /** 1351 | * @param {function()} successCallback 1352 | * @param {function(!chrome.cast.Error)} errorCallback 1353 | */ 1354 | queuePrev( 1355 | successCallback: Function, 1356 | errorCallback: (error: chrome.cast.Error) => void 1357 | ): void 1358 | 1359 | /** 1360 | * @param {!number} itemId 1361 | * @param {function()} successCallback 1362 | * @param {function(!chrome.cast.Error)} errorCallback 1363 | */ 1364 | queueRemoveItem( 1365 | itemId: number, 1366 | successCallback: Function, 1367 | errorCallback: (error: chrome.cast.Error) => void 1368 | ): void 1369 | 1370 | /** 1371 | * @param {!chrome.cast.media.QueueReorderItemsRequest} queueReorderItemsRequest 1372 | * @param {function()} successCallback 1373 | * @param {function(!chrome.cast.Error)} errorCallback 1374 | */ 1375 | queueReorderItems( 1376 | queueReorderItemsRequest: chrome.cast.media.QueueReorderItemsRequest, 1377 | successCallback: Function, 1378 | errorCallback: (error: chrome.cast.Error) => void 1379 | ): void 1380 | 1381 | /** 1382 | * @param {!chrome.cast.media.RepeatMode} repeatMode 1383 | * @param {function()} successCallback 1384 | * @param {function(!chrome.cast.Error)} errorCallback 1385 | */ 1386 | queueSetRepeatMode( 1387 | repeatMode: chrome.cast.media.RepeatMode, 1388 | successCallback: Function, 1389 | errorCallback: (error: chrome.cast.Error) => void 1390 | ): void 1391 | 1392 | /** 1393 | * @param {!chrome.cast.media.QueueUpdateItemsRequest} queueUpdateItemsRequest 1394 | * @param {function()} successCallback 1395 | * @param {function(!chrome.cast.Error)} errorCallback 1396 | */ 1397 | queueUpdateItems( 1398 | queueUpdateItemsRequest: chrome.cast.media.QueueUpdateItemsRequest, 1399 | successCallback: Function, 1400 | errorCallback: (error: chrome.cast.Error) => void 1401 | ): void 1402 | 1403 | } 1404 | 1405 | export class Track { 1406 | /** 1407 | * @param {number} trackId 1408 | * @param {!chrome.cast.media.TrackType} trackType 1409 | * @constructor 1410 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Track 1411 | */ 1412 | constructor( 1413 | trackId: number, 1414 | trackType: chrome.cast.media.TrackType 1415 | ); 1416 | 1417 | trackId: number; 1418 | trackContentId: string; 1419 | trackContentType: string; 1420 | type: chrome.cast.media.TrackType; 1421 | name: string; 1422 | language: string; 1423 | subtype: chrome.cast.media.TextTrackType; 1424 | customData: Object; 1425 | } 1426 | 1427 | export class TextTrackStyle { 1428 | /** 1429 | * @constructor 1430 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.TextTrackStyle 1431 | */ 1432 | constructor(); 1433 | 1434 | foregroundColor: string; 1435 | backgroundColor: string; 1436 | edgeType: chrome.cast.media.TextTrackEdgeType; 1437 | edgeColor: string; 1438 | windowType: chrome.cast.media.TextTrackWindowType; 1439 | windowColor: string; 1440 | windowRoundedCornerRadius: number; 1441 | fontScale: number; 1442 | fontFamily: string; 1443 | fontGenericFamily: chrome.cast.media.TextTrackFontGenericFamily; 1444 | fontStyle: chrome.cast.media.TextTrackFontStyle; 1445 | customData: Object; 1446 | } 1447 | } 1448 | 1449 | /** 1450 | * @namespace 1451 | * @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.timeout 1452 | */ 1453 | declare namespace chrome.cast.media.timeout { 1454 | export var load: number; 1455 | export var getStatus: number; 1456 | export var play: number; 1457 | export var pause: number; 1458 | export var seek: number; 1459 | export var stop: number; 1460 | export var setVolume: number; 1461 | export var editTracksInfo: number; 1462 | export var queueInsert: number; 1463 | export var queueLoad: number; 1464 | export var queueRemove: number; 1465 | export var queueReorder: number; 1466 | export var queueUpdate: number; 1467 | } 1468 | 1469 | --------------------------------------------------------------------------------