@silvermine/videojs-chromecast
Advanced tools
Comparing version 1.4.0 to 1.4.1
@@ -6,2 +6,10 @@ # Changelog | ||
### [1.4.1](https://github.com/silvermine/videojs-chromecast/compare/v1.4.0...v1.4.1) (2023-03-21) | ||
### Bug Fixes | ||
* remove deprecated `.extend` method ([994b5b9](https://github.com/silvermine/videojs-chromecast/commit/994b5b9ae6df89f657e2ff4a920056826094b54f)), closes [#152](https://github.com/silvermine/videojs-chromecast/issues/152) [#147](https://github.com/silvermine/videojs-chromecast/issues/147) | ||
## [1.4.0](https://github.com/silvermine/videojs-chromecast/compare/v1.3.4...v1.4.0) (2023-03-21) | ||
@@ -8,0 +16,0 @@ |
{ | ||
"name": "@silvermine/videojs-chromecast", | ||
"version": "1.4.0", | ||
"version": "1.4.1", | ||
"description": "video.js plugin for casting to chromecast", | ||
@@ -71,4 +71,4 @@ "main": "src/js/index.js", | ||
"peerDependencies": { | ||
"video.js": ">= 6 < 8" | ||
"video.js": ">= 6 < 9" | ||
} | ||
} |
@@ -1,183 +0,181 @@ | ||
/** | ||
* The ChromecastButton module contains both the ChromecastButton class definition and | ||
* the function used to register the button as a Video.js Component. | ||
* | ||
* @module ChromecastButton | ||
*/ | ||
module.exports = function(videojs) { | ||
var ChromecastButton; | ||
/** | ||
* Registers the ChromecastButton Component with Video.js. Calls | ||
* {@link http://docs.videojs.com/Component.html#.registerComponent}, which will add a | ||
* component called `chromecastButton` to the list of globally registered Video.js | ||
* components. The `chromecastButton` is added to the player's control bar UI | ||
* automatically once {@link module:enableChromecast} has been called. If you would | ||
* like to specify the order of the buttons that appear in the control bar, including | ||
* this button, you can do so in the options that you pass to the `videojs` function | ||
* when creating a player: | ||
* | ||
* ``` | ||
* videojs('playerID', { | ||
* controlBar: { | ||
* children: [ | ||
* 'playToggle', | ||
* 'progressControl', | ||
* 'volumePanel', | ||
* 'fullscreenToggle', | ||
* 'chromecastButton', | ||
* ], | ||
* } | ||
* }); | ||
* ``` | ||
* | ||
* @param videojs {object} A reference to {@link http://docs.videojs.com/module-videojs.html|Video.js} | ||
* @see http://docs.videojs.com/module-videojs.html#~registerPlugin | ||
*/ | ||
/** | ||
* The Video.js Button class is the base class for UI button components. | ||
* | ||
* @external Button | ||
* @see {@link http://docs.videojs.com/Button.html|Button} | ||
*/ | ||
/** @lends ChromecastButton.prototype */ | ||
ChromecastButton = { | ||
/** | ||
* This class is a button component designed to be displayed in the player UI's control | ||
* bar. It opens the Chromecast menu when clicked. | ||
* The Video.js Button class is the base class for UI button components. | ||
* | ||
* @constructs | ||
* @extends external:Button | ||
* @param player {Player} the video.js player instance | ||
* @external Button | ||
* @see {@link http://docs.videojs.com/Button.html|Button} | ||
*/ | ||
constructor: function(player, options) { | ||
this.constructor.super_.apply(this, arguments); | ||
const ButtonComponent = videojs.getComponent('Button'); | ||
player.on('chromecastConnected', this._onChromecastConnected.bind(this)); | ||
player.on('chromecastDisconnected', this._onChromecastDisconnected.bind(this)); | ||
player.on('chromecastDevicesAvailable', this._onChromecastDevicesAvailable.bind(this)); | ||
player.on('chromecastDevicesUnavailable', this._onChromecastDevicesUnavailable.bind(this)); | ||
/** | ||
* The ChromecastButton module contains both the ChromecastButton class definition and | ||
* the function used to register the button as a Video.js Component. | ||
* @module ChromecastButton | ||
*/ | ||
// Use the initial state of `hasAvailableDevices` to call the corresponding event | ||
// handlers because the corresponding events may have already been emitted before | ||
// binding the listeners above. | ||
if (player.chromecastSessionManager && player.chromecastSessionManager.hasAvailableDevices()) { | ||
this._onChromecastDevicesAvailable(); | ||
} else { | ||
this._onChromecastDevicesUnavailable(); | ||
} | ||
if (options.addCastLabelToButton) { | ||
this.el().classList.add('vjs-chromecast-button-lg'); | ||
/** @lends ChromecastButton.prototype **/ | ||
class ChromecastButton extends ButtonComponent { | ||
this._labelEl = document.createElement('span'); | ||
this._labelEl.classList.add('vjs-chromecast-button-label'); | ||
this._updateCastLabelText(); | ||
/** | ||
* This class is a button component designed to be displayed in the | ||
* player UI's control bar. It opens the Chromecast menu when clicked. | ||
* | ||
* @constructs | ||
* @extends external:Button | ||
* @param player {Player} the video.js player instance | ||
*/ | ||
constructor(player, options) { | ||
super(player, options); | ||
this.el().appendChild(this._labelEl); | ||
} else { | ||
this.controlText('Open Chromecast menu'); | ||
player.on('chromecastConnected', this._onChromecastConnected.bind(this)); | ||
player.on('chromecastDisconnected', this._onChromecastDisconnected.bind(this)); | ||
player.on('chromecastDevicesAvailable', this._onChromecastDevicesAvailable.bind(this)); | ||
player.on('chromecastDevicesUnavailable', this._onChromecastDevicesUnavailable.bind(this)); | ||
// Use the initial state of `hasAvailableDevices` to call the corresponding event | ||
// handlers because the corresponding events may have already been emitted before | ||
// binding the listeners above. | ||
if (player.chromecastSessionManager && player.chromecastSessionManager.hasAvailableDevices()) { | ||
this._onChromecastDevicesAvailable(); | ||
} else { | ||
this._onChromecastDevicesUnavailable(); | ||
} | ||
if (options.addCastLabelToButton) { | ||
this.el().classList.add('vjs-chromecast-button-lg'); | ||
this._labelEl = document.createElement('span'); | ||
this._labelEl.classList.add('vjs-chromecast-button-label'); | ||
this._updateCastLabelText(); | ||
this.el().appendChild(this._labelEl); | ||
} else { | ||
this.controlText('Open Chromecast menu'); | ||
} | ||
} | ||
}, | ||
/** | ||
* Overrides Button#buildCSSClass to return the classes used on the button element. | ||
* | ||
* @param el {DOMElement} | ||
* @see {@link http://docs.videojs.com/Button.html#buildCSSClass|Button#buildCSSClass} | ||
*/ | ||
buildCSSClass: function() { | ||
return 'vjs-chromecast-button ' + | ||
(this._isChromecastConnected ? 'vjs-chromecast-casting-state ' : '') + | ||
(this.options_.addCastLabelToButton ? 'vjs-chromecast-button-lg ' : '') + | ||
this.constructor.super_.prototype.buildCSSClass(); | ||
}, | ||
/** | ||
* Overrides Button#buildCSSClass to return the classes used on the button element. | ||
* | ||
* @param el {DOMElement} | ||
* @see {@link http://docs.videojs.com/Button.html#buildCSSClass|Button#buildCSSClass} | ||
*/ | ||
buildCSSClass() { | ||
return 'vjs-chromecast-button ' + | ||
(this._isChromecastConnected ? 'vjs-chromecast-casting-state ' : '') + | ||
(this.options_.addCastLabelToButton ? 'vjs-chromecast-button-lg ' : '') + | ||
ButtonComponent.prototype.buildCSSClass(); | ||
} | ||
/** | ||
* Overrides Button#handleClick to handle button click events. Chromecast functionality | ||
* is handled outside of this class, which should be limited to UI related logic. This | ||
* function simply triggers an event on the player. | ||
* | ||
* @fires ChromecastButton#chromecastRequested | ||
* @param el {DOMElement} | ||
* @see {@link http://docs.videojs.com/Button.html#handleClick|Button#handleClick} | ||
*/ | ||
handleClick: function() { | ||
this.player().trigger('chromecastRequested'); | ||
}, | ||
/** | ||
* Overrides Button#handleClick to handle button click events. Chromecast | ||
* functionality is handled outside of this class, which should be limited | ||
* to UI related logic. This function simply triggers an event on the player. | ||
* | ||
* @fires ChromecastButton#chromecastRequested | ||
* @param el {DOMElement} | ||
* @see {@link http://docs.videojs.com/Button.html#handleClick|Button#handleClick} | ||
*/ | ||
handleClick() { | ||
this.player().trigger('chromecastRequested'); | ||
} | ||
/** | ||
* Handles `chromecastConnected` player events. | ||
* | ||
* @private | ||
*/ | ||
_onChromecastConnected: function() { | ||
this._isChromecastConnected = true; | ||
this._reloadCSSClasses(); | ||
this._updateCastLabelText(); | ||
}, | ||
/** | ||
* Handles `chromecastConnected` player events. | ||
* | ||
* @private | ||
*/ | ||
_onChromecastConnected() { | ||
this._isChromecastConnected = true; | ||
this._reloadCSSClasses(); | ||
this._updateCastLabelText(); | ||
} | ||
/** | ||
* Handles `chromecastDisconnected` player events. | ||
* | ||
* @private | ||
*/ | ||
_onChromecastDisconnected: function() { | ||
this._isChromecastConnected = false; | ||
this._reloadCSSClasses(); | ||
this._updateCastLabelText(); | ||
}, | ||
/** | ||
* Handles `chromecastDisconnected` player events. | ||
* | ||
* @private | ||
*/ | ||
_onChromecastDisconnected() { | ||
this._isChromecastConnected = false; | ||
this._reloadCSSClasses(); | ||
this._updateCastLabelText(); | ||
} | ||
/** | ||
* Handles `chromecastDevicesAvailable` player events. | ||
* | ||
* @private | ||
*/ | ||
_onChromecastDevicesAvailable: function() { | ||
this.show(); | ||
}, | ||
/** | ||
* Handles `chromecastDevicesAvailable` player events. | ||
* | ||
* @private | ||
*/ | ||
_onChromecastDevicesAvailable() { | ||
this.show(); | ||
} | ||
/** | ||
* Handles `chromecastDevicesUnavailable` player events. | ||
* | ||
* @private | ||
*/ | ||
_onChromecastDevicesUnavailable: function() { | ||
this.hide(); | ||
}, | ||
/** | ||
* Handles `chromecastDevicesUnavailable` player events. | ||
* | ||
* @private | ||
*/ | ||
_onChromecastDevicesUnavailable() { | ||
this.hide(); | ||
} | ||
/** | ||
* Re-calculates which CSS classes the button needs and sets them on the buttons' | ||
* DOMElement. | ||
* | ||
* @private | ||
*/ | ||
_reloadCSSClasses: function() { | ||
if (!this.el_) { | ||
return; | ||
/** | ||
* Re-calculates which CSS classes the button needs and sets them on the buttons' | ||
* DOMElement. | ||
* | ||
* @private | ||
*/ | ||
_reloadCSSClasses() { | ||
if (!this.el_) { | ||
return; | ||
} | ||
this.el_.className = this.buildCSSClass(); | ||
} | ||
this.el_.className = this.buildCSSClass(); | ||
}, | ||
/** | ||
* Updates the optional cast label text based on whether the chromecast is connected | ||
* or disconnected. | ||
* | ||
* @private | ||
*/ | ||
_updateCastLabelText: function() { | ||
if (!this._labelEl) { | ||
return; | ||
/** | ||
* Updates the optional cast label text based on whether the chromecast is connected | ||
* or disconnected. | ||
* | ||
* @private | ||
*/ | ||
_updateCastLabelText() { | ||
if (!this._labelEl) { | ||
return; | ||
} | ||
this._labelEl.textContent = this._isChromecastConnected ? this.localize('Disconnect Cast') : this.localize('Cast'); | ||
} | ||
this._labelEl.textContent = this._isChromecastConnected ? this.localize('Disconnect Cast') : this.localize('Cast'); | ||
}, | ||
}; | ||
} | ||
/** | ||
* Registers the ChromecastButton Component with Video.js. Calls | ||
* {@link http://docs.videojs.com/Component.html#.registerComponent}, which will add a | ||
* component called `chromecastButton` to the list of globally registered Video.js | ||
* components. The `chromecastButton` is added to the player's control bar UI | ||
* automatically once {@link module:enableChromecast} has been called. If you would like | ||
* to specify the order of the buttons that appear in the control bar, including this | ||
* button, you can do so in the options that you pass to the `videojs` function when | ||
* creating a player: | ||
* | ||
* ``` | ||
* videojs('playerID', { | ||
* controlBar: { | ||
* children: [ | ||
* 'playToggle', | ||
* 'progressControl', | ||
* 'volumePanel', | ||
* 'fullscreenToggle', | ||
* 'chromecastButton', | ||
* ], | ||
* } | ||
* }); | ||
* ``` | ||
* | ||
* @param videojs {object} A reference to {@link http://docs.videojs.com/module-videojs.html|Video.js} | ||
* @see http://docs.videojs.com/module-videojs.html#~registerPlugin | ||
*/ | ||
module.exports = function(videojs) { | ||
var ChromecastButtonImpl; | ||
ChromecastButtonImpl = videojs.extend(videojs.getComponent('Button'), ChromecastButton); | ||
videojs.registerComponent('chromecastButton', ChromecastButtonImpl); | ||
videojs.registerComponent('chromecastButton', ChromecastButton); | ||
}; |
var ChromecastSessionManager = require('../chromecast/ChromecastSessionManager'), | ||
ChromecastTechUI = require('./ChromecastTechUI'), | ||
SESSION_TIMEOUT = 10 * 1000, // milliseconds | ||
ChromecastTech; | ||
ChromecastTechUI = require('./ChromecastTechUI'); | ||
/** | ||
* @module ChomecastTech | ||
*/ | ||
/** | ||
* The Video.js Tech class is the base class for classes that provide media playback | ||
* technology implementations to Video.js such as HTML5, Flash and HLS. | ||
* Registers the ChromecastTech Tech with Video.js. Calls {@link | ||
* http://docs.videojs.com/Tech.html#.registerTech}, which will add a Tech called | ||
* `chromecast` to the list of globally registered Video.js Tech implementations. | ||
* | ||
* @external Tech | ||
* @see {@link http://docs.videojs.com/Tech.html|Tech} | ||
* [Video.js Tech](http://docs.videojs.com/Tech.html) are initialized and used | ||
* automatically by Video.js Player instances. Whenever a new source is set on the player, | ||
* the player iterates through the list of available Tech to determine which to use to | ||
* play the source. | ||
* | ||
* @param videojs {object} A reference to | ||
* {@link http://docs.videojs.com/module-videojs.html|Video.js} | ||
* @see http://docs.videojs.com/Tech.html#.registerTech | ||
*/ | ||
module.exports = function(videojs) { | ||
var Tech = videojs.getComponent('Tech'), | ||
SESSION_TIMEOUT = 10 * 1000; // milliseconds | ||
/** @lends ChromecastTech.prototype */ | ||
ChromecastTech = { | ||
/** | ||
* @module ChomecastTech | ||
*/ | ||
/** | ||
* Implements Video.js playback {@link http://docs.videojs.com/tutorial-tech_.html|Tech} | ||
* for {@link https://developers.google.com/cast/|Google's Chromecast}. | ||
* The Video.js Tech class is the base class for classes that provide media playback | ||
* technology implementations to Video.js such as HTML5, Flash and HLS. | ||
* | ||
* @constructs ChromecastTech | ||
* @extends external:Tech | ||
* @param options {object} The options to use for configuration | ||
* @see {@link https://developers.google.com/cast/|Google Cast} | ||
* @external Tech | ||
* @see {@link http://docs.videojs.com/Tech.html|Tech} | ||
*/ | ||
constructor: function(options) { | ||
var subclass; | ||
this._eventListeners = []; | ||
/** @lends ChromecastTech.prototype */ | ||
class ChromecastTech extends Tech { | ||
this.videojsPlayer = this.videojs(options.playerId); | ||
this._chromecastSessionManager = this.videojsPlayer.chromecastSessionManager; | ||
/** | ||
* Implements Video.js playback {@link http://docs.videojs.com/tutorial-tech_.html|Tech} | ||
* for {@link https://developers.google.com/cast/|Google's Chromecast}. | ||
* | ||
* @constructs ChromecastTech | ||
* @extends external:Tech | ||
* @param options {object} The options to use for configuration | ||
* @see {@link https://developers.google.com/cast/|Google Cast} | ||
*/ | ||
constructor(options) { | ||
super(options); | ||
// We have to initialize the UI here, before calling super.constructor | ||
// because the constructor calls `createEl`, which references `this._ui`. | ||
this._ui = new ChromecastTechUI(); | ||
this._ui.updatePoster(this.videojsPlayer.poster()); | ||
this.featuresVolumeControl = true; | ||
this.featuresPlaybackRate = false; | ||
this.movingMediaElementInDOM = false; | ||
this.featuresFullscreenResize = true; | ||
this.featuresTimeupdateEvents = true; | ||
this.featuresProgressEvents = false; | ||
// Text tracks are not supported in this version | ||
this.featuresNativeTextTracks = false; | ||
this.featuresNativeAudioTracks = false; | ||
this.featuresNativeVideoTracks = false; | ||
// Call the super class' constructor function | ||
subclass = this.constructor.super_.apply(this, arguments); | ||
// Give ChromecastTech class instances a reference to videojs | ||
this.videojs = videojs; | ||
this._eventListeners = []; | ||
this._remotePlayer = this._chromecastSessionManager.getRemotePlayer(); | ||
this._remotePlayerController = this._chromecastSessionManager.getRemotePlayerController(); | ||
this._listenToPlayerControllerEvents(); | ||
this.on('dispose', this._removeAllEventListeners.bind(this)); | ||
this.videojsPlayer = this.videojs(options.playerId); | ||
this._chromecastSessionManager = this.videojsPlayer.chromecastSessionManager; | ||
this._hasPlayedAnyItem = false; | ||
this._requestTitle = options.requestTitleFn || function() { /* noop */ }; | ||
this._requestSubtitle = options.requestSubtitleFn || function() { /* noop */ }; | ||
this._requestCustomData = options.requestCustomDataFn || function() { /* noop */ }; | ||
// See `currentTime` function | ||
this._initialStartTime = options.startTime || 0; | ||
this._ui.updatePoster(this.videojsPlayer.poster()); | ||
this._playSource(options.source, this._initialStartTime); | ||
this.ready(function() { | ||
this.setMuted(options.muted); | ||
}.bind(this)); | ||
this._remotePlayer = this._chromecastSessionManager.getRemotePlayer(); | ||
this._remotePlayerController = this._chromecastSessionManager.getRemotePlayerController(); | ||
this._listenToPlayerControllerEvents(); | ||
this.on('dispose', this._removeAllEventListeners.bind(this)); | ||
return subclass; | ||
}, | ||
this._hasPlayedAnyItem = false; | ||
this._requestTitle = options.requestTitleFn || function() { /* noop */ }; | ||
this._requestSubtitle = options.requestSubtitleFn || function() { /* noop */ }; | ||
this._requestCustomData = options.requestCustomDataFn || function() { /* noop */ }; | ||
// See `currentTime` function | ||
this._initialStartTime = options.startTime || 0; | ||
/** | ||
* Creates a DOMElement that Video.js displays in its player UI while this Tech is | ||
* active. | ||
* | ||
* @returns {DOMElement} | ||
* @see {@link http://docs.videojs.com/Tech.html#createEl} | ||
*/ | ||
createEl: function() { | ||
return this._ui.getDOMElement(); | ||
}, | ||
this._playSource(options.source, this._initialStartTime); | ||
this.ready(function() { | ||
this.setMuted(options.muted); | ||
}.bind(this)); | ||
} | ||
/** | ||
* Resumes playback if a media item is paused or restarts an item from its beginning if | ||
* the item has played and ended. | ||
* | ||
* @see {@link http://docs.videojs.com/Player.html#play} | ||
*/ | ||
play: function() { | ||
if (!this.paused()) { | ||
return; | ||
/** | ||
* Creates a DOMElement that Video.js displays in its player UI while this Tech is | ||
* active. | ||
* | ||
* @returns {DOMElement} | ||
* @see {@link http://docs.videojs.com/Tech.html#createEl} | ||
*/ | ||
createEl() { | ||
// We have to initialize the UI here, because the super.constructor | ||
// calls `createEl`, which references `this._ui`. | ||
this._ui = this._ui || new ChromecastTechUI(); | ||
return this._ui.getDOMElement(); | ||
} | ||
if (this.ended() && !this._isMediaLoading) { | ||
// Restart the current item from the beginning | ||
this._playSource({ src: this.videojsPlayer.src() }, 0); | ||
} else { | ||
this._remotePlayerController.playOrPause(); | ||
/** | ||
* Resumes playback if a media item is paused or restarts an item from | ||
* its beginning if the item has played and ended. | ||
* | ||
* @see {@link http://docs.videojs.com/Player.html#play} | ||
*/ | ||
play() { | ||
if (!this.paused()) { | ||
return; | ||
} | ||
if (this.ended() && !this._isMediaLoading) { | ||
// Restart the current item from the beginning | ||
this._playSource({ src: this.videojsPlayer.src() }, 0); | ||
} else { | ||
this._remotePlayerController.playOrPause(); | ||
} | ||
} | ||
}, | ||
/** | ||
* Pauses playback if the player is not already paused and if the current media item | ||
* has not ended yet. | ||
* | ||
* @see {@link http://docs.videojs.com/Player.html#pause} | ||
*/ | ||
pause: function() { | ||
if (!this.paused() && this._remotePlayer.canPause) { | ||
this._remotePlayerController.playOrPause(); | ||
/** | ||
* Pauses playback if the player is not already paused and if the current media item | ||
* has not ended yet. | ||
* | ||
* @see {@link http://docs.videojs.com/Player.html#pause} | ||
*/ | ||
pause() { | ||
if (!this.paused() && this._remotePlayer.canPause) { | ||
this._remotePlayerController.playOrPause(); | ||
} | ||
} | ||
}, | ||
/** | ||
* Returns whether or not the player is "paused". Video.js' definition of "paused" is | ||
* "playback paused" OR "not playing". | ||
* | ||
* @returns {boolean} true if playback is paused | ||
* @see {@link http://docs.videojs.com/Player.html#paused} | ||
*/ | ||
paused: function() { | ||
return this._remotePlayer.isPaused || this.ended() || this._remotePlayer.playerState === null; | ||
}, | ||
/** | ||
* Returns whether or not the player is "paused". Video.js' | ||
* definition of "paused" is "playback paused" OR "not playing". | ||
* | ||
* @returns {boolean} true if playback is paused | ||
* @see {@link http://docs.videojs.com/Player.html#paused} | ||
*/ | ||
paused() { | ||
return this._remotePlayer.isPaused || this.ended() || this._remotePlayer.playerState === null; | ||
} | ||
/** | ||
* Stores the given source and begins playback, starting at the beginning | ||
* of the media item. | ||
* | ||
* @param source {object} the source to store and play | ||
* @see {@link http://docs.videojs.com/Player.html#src} | ||
*/ | ||
setSource: function(source) { | ||
if (this._currentSource && this._currentSource.src === source.src && this._currentSource.type === source.type) { | ||
// Skip setting the source if the `source` argument is the same as what's already | ||
// been set. This `setSource` function calls `this._playSource` which sends a | ||
// "load media" request to the Chromecast PlayerController. Because this function | ||
// may be called multiple times in rapid succession with the same `source` | ||
// argument, we need to de-duplicate calls with the same `source` argument to | ||
// prevent overwhelming the Chromecast PlayerController with expensive "load | ||
// media" requests, which it itself does not de-duplicate. | ||
return; | ||
/** | ||
* Stores the given source and begins playback, starting at the beginning | ||
* of the media item. | ||
* | ||
* @param source {object} the source to store and play | ||
* @see {@link http://docs.videojs.com/Player.html#src} | ||
*/ | ||
setSource(source) { | ||
if (this._currentSource && this._currentSource.src === source.src && this._currentSource.type === source.type) { | ||
// Skip setting the source if the `source` argument is the | ||
// same as what's already been set. This `setSource` function | ||
// calls `this._playSource` which sends a "load media" request | ||
// to the Chromecast PlayerController. Because this function | ||
// may be called multiple times in rapid succession with the same `source` | ||
// argument, we need to de-duplicate calls with the same `source` argument to | ||
// prevent overwhelming the Chromecast PlayerController with expensive "load | ||
// media" requests, which it itself does not de-duplicate. | ||
return; | ||
} | ||
// We cannot use `this.videojsPlayer.currentSource()` because the | ||
// value returned by that function is not the same as what's returned | ||
// by the Video.js Player's middleware after they are run. Also, simply | ||
// using `this.videojsPlayer.src()` does not include mimetype information | ||
// which we pass to the Chromecast player. | ||
this._currentSource = source; | ||
this._playSource(source, 0); | ||
} | ||
// We cannot use `this.videojsPlayer.currentSource()` because the value returned by | ||
// that function is not the same as what's returned by the Video.js Player's | ||
// middleware after they are run. Also, simply using `this.videojsPlayer.src()` | ||
// does not include mimetype information which we pass to the Chromecast player. | ||
this._currentSource = source; | ||
this._playSource(source, 0); | ||
}, | ||
/** | ||
* Plays the given source, beginning at an optional starting time. | ||
* | ||
* @private | ||
* @param source {object} the source to play | ||
* @param [startTime] The time to start playback at, in seconds | ||
* @see {@link http://docs.videojs.com/Player.html#src} | ||
*/ | ||
_playSource: function(source, startTime) { | ||
var castSession = this._getCastSession(), | ||
mediaInfo = new chrome.cast.media.MediaInfo(source.src, source.type), | ||
title = this._requestTitle(source), | ||
subtitle = this._requestSubtitle(source), | ||
poster = this.poster(), | ||
customData = this._requestCustomData(source), | ||
request; | ||
/** | ||
* Plays the given source, beginning at an optional starting time. | ||
* | ||
* @private | ||
* @param source {object} the source to play | ||
* @param [startTime] The time to start playback at, in seconds | ||
* @see {@link http://docs.videojs.com/Player.html#src} | ||
*/ | ||
_playSource(source, startTime) { | ||
var castSession = this._getCastSession(), | ||
mediaInfo = new chrome.cast.media.MediaInfo(source.src, source.type), | ||
title = this._requestTitle(source), | ||
subtitle = this._requestSubtitle(source), | ||
poster = this.poster(), | ||
customData = this._requestCustomData(source), | ||
request; | ||
this.trigger('waiting'); | ||
this._clearSessionTimeout(); | ||
this.trigger('waiting'); | ||
this._clearSessionTimeout(); | ||
mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata(); | ||
mediaInfo.metadata.metadataType = chrome.cast.media.MetadataType.GENERIC; | ||
mediaInfo.metadata.title = title; | ||
mediaInfo.metadata.subtitle = subtitle; | ||
mediaInfo.streamType = this.videojsPlayer.liveTracker && this.videojsPlayer.liveTracker.isLive() | ||
? chrome.cast.media.StreamType.LIVE | ||
: chrome.cast.media.StreamType.BUFFERED; | ||
mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata(); | ||
mediaInfo.metadata.metadataType = chrome.cast.media.MetadataType.GENERIC; | ||
mediaInfo.metadata.title = title; | ||
mediaInfo.metadata.subtitle = subtitle; | ||
mediaInfo.streamType = this.videojsPlayer.liveTracker && this.videojsPlayer.liveTracker.isLive() | ||
? chrome.cast.media.StreamType.LIVE | ||
: chrome.cast.media.StreamType.BUFFERED; | ||
if (poster) { | ||
mediaInfo.metadata.images = [ { url: poster } ]; | ||
} | ||
if (customData) { | ||
mediaInfo.customData = customData; | ||
} | ||
if (poster) { | ||
mediaInfo.metadata.images = [ { url: poster } ]; | ||
} | ||
if (customData) { | ||
mediaInfo.customData = customData; | ||
} | ||
this._ui.updateTitle(title); | ||
this._ui.updateSubtitle(subtitle); | ||
this._ui.updateTitle(title); | ||
this._ui.updateSubtitle(subtitle); | ||
request = new chrome.cast.media.LoadRequest(mediaInfo); | ||
request.autoplay = true; | ||
request.currentTime = startTime; | ||
request = new chrome.cast.media.LoadRequest(mediaInfo); | ||
request.autoplay = true; | ||
request.currentTime = startTime; | ||
this._isMediaLoading = true; | ||
this._hasPlayedCurrentItem = false; | ||
castSession.loadMedia(request) | ||
.then(function() { | ||
if (!this._hasPlayedAnyItem) { | ||
// `triggerReady` is required here to notify the Video.js player that the | ||
// Tech has been initialized and is ready. | ||
this.triggerReady(); | ||
} | ||
this.trigger('loadstart'); | ||
this.trigger('loadeddata'); | ||
this.trigger('play'); | ||
this.trigger('playing'); | ||
this._hasPlayedAnyItem = true; | ||
this._isMediaLoading = false; | ||
this._getMediaSession().addUpdateListener(this._onMediaSessionStatusChanged.bind(this)); | ||
}.bind(this), this._triggerErrorEvent.bind(this)); | ||
}, | ||
this._isMediaLoading = true; | ||
this._hasPlayedCurrentItem = false; | ||
castSession.loadMedia(request) | ||
.then(function() { | ||
if (!this._hasPlayedAnyItem) { | ||
// `triggerReady` is required here to notify the Video.js | ||
// player that the Tech has been initialized and is ready. | ||
this.triggerReady(); | ||
} | ||
this.trigger('loadstart'); | ||
this.trigger('loadeddata'); | ||
this.trigger('play'); | ||
this.trigger('playing'); | ||
this._hasPlayedAnyItem = true; | ||
this._isMediaLoading = false; | ||
this._getMediaSession().addUpdateListener(this._onMediaSessionStatusChanged.bind(this)); | ||
}.bind(this), this._triggerErrorEvent.bind(this)); | ||
} | ||
/** | ||
* Manually updates the current time. The playback position will jump to the given time | ||
* and continue playing if the item was playing when `setCurrentTime` was called, or | ||
* remain paused if the item was paused. | ||
* | ||
* @param time {number} the playback time position to jump to | ||
* @see {@link http://docs.videojs.com/Tech.html#setCurrentTime} | ||
*/ | ||
setCurrentTime: function(time) { | ||
var duration = this.duration(); | ||
/** | ||
* Manually updates the current time. The playback position will jump to | ||
* the given time and continue playing if the item was playing when `setCurrentTime` | ||
* was called, or remain paused if the item was paused. | ||
* | ||
* @param time {number} the playback time position to jump to | ||
* @see {@link http://docs.videojs.com/Tech.html#setCurrentTime} | ||
*/ | ||
setCurrentTime(time) { | ||
var duration = this.duration(); | ||
if (time > duration || !this._remotePlayer.canSeek) { | ||
return; | ||
if (time > duration || !this._remotePlayer.canSeek) { | ||
return; | ||
} | ||
// Seeking to any place within (approximately) 1 second of the end of the item | ||
// causes the Video.js player to get stuck in a BUFFERING state. To work around | ||
// this, we only allow seeking to within 1 second of the end of an item. | ||
this._remotePlayer.currentTime = Math.min(duration - 1, time); | ||
this._remotePlayerController.seek(); | ||
this._triggerTimeUpdateEvent(); | ||
} | ||
// Seeking to any place within (approximately) 1 second of the end of the item | ||
// causes the Video.js player to get stuck in a BUFFERING state. To work around | ||
// this, we only allow seeking to within 1 second of the end of an item. | ||
this._remotePlayer.currentTime = Math.min(duration - 1, time); | ||
this._remotePlayerController.seek(); | ||
this._triggerTimeUpdateEvent(); | ||
}, | ||
/** | ||
* Returns the current playback time position. | ||
* | ||
* @returns {number} the current playback time position | ||
* @see {@link http://docs.videojs.com/Player.html#currentTime} | ||
*/ | ||
currentTime: function() { | ||
// There is a brief period of time when Video.js has switched to the chromecast | ||
// Tech, but chromecast has not yet loaded its first media item. During that time, | ||
// Video.js calls this `currentTime` function to update its player UI. In that | ||
// period, `this._remotePlayer.currentTime` will be 0 because the media has not | ||
// loaded yet. To prevent the UI from using a 0 second currentTime, we use the | ||
// currentTime passed in to the first media item that was provided to the Tech until | ||
// chromecast plays its first item. | ||
if (!this._hasPlayedAnyItem) { | ||
return this._initialStartTime; | ||
/** | ||
* Returns the current playback time position. | ||
* | ||
* @returns {number} the current playback time position | ||
* @see {@link http://docs.videojs.com/Player.html#currentTime} | ||
*/ | ||
currentTime() { | ||
// There is a brief period of time when Video.js has switched to the chromecast | ||
// Tech, but chromecast has not yet loaded its first media item. During | ||
// that time, Video.js calls this `currentTime` function to update | ||
// its player UI. In that period, `this._remotePlayer.currentTime` | ||
// will be 0 because the media has not loaded yet. To prevent the | ||
// UI from using a 0 second currentTime, we use the currentTime passed | ||
// in to the first media item that was provided to the Tech until | ||
// chromecast plays its first item. | ||
if (!this._hasPlayedAnyItem) { | ||
return this._initialStartTime; | ||
} | ||
return this._remotePlayer.currentTime; | ||
} | ||
return this._remotePlayer.currentTime; | ||
}, | ||
/** | ||
* Returns the duration of the current media item, or `0` if the source is not set or | ||
* if the duration of the item is not available from the Chromecast API yet. | ||
* | ||
* @returns {number} the duration of the current media item | ||
* @see {@link http://docs.videojs.com/Player.html#duration} | ||
*/ | ||
duration: function() { | ||
// There is a brief period of time when Video.js has switched to the chromecast | ||
// Tech, but chromecast has not yet loaded its first media item. During that time, | ||
// Video.js calls this `duration` function to update its player UI. In that period, | ||
// `this._remotePlayer.duration` will be 0 because the media has not loaded yet. To | ||
// prevent the UI from using a 0 second duration, we use the duration passed in to | ||
// the first media item that was provided to the Tech until chromecast plays its | ||
// first item. | ||
if (!this._hasPlayedAnyItem) { | ||
return this.videojsPlayer.duration(); | ||
/** | ||
* Returns the duration of the current media item, or `0` if the source | ||
* is not set or if the duration of the item is not available from the | ||
* Chromecast API yet. | ||
* | ||
* @returns {number} the duration of the current media item | ||
* @see {@link http://docs.videojs.com/Player.html#duration} | ||
*/ | ||
duration() { | ||
// There is a brief period of time when Video.js has switched to the chromecast | ||
// Tech, but chromecast has not yet loaded its first media item. | ||
// During that time, Video.js calls this `duration` function to update its player | ||
// UI. In that period, `this._remotePlayer.duration` will be 0 because the media | ||
// has not loaded yet. To prevent the UI from using a 0 second duration, we | ||
// use the duration passed in to the first media item that was provided to | ||
// the Tech until chromecast plays its first item. | ||
if (!this._hasPlayedAnyItem) { | ||
return this.videojsPlayer.duration(); | ||
} | ||
return this._remotePlayer.duration; | ||
} | ||
return this._remotePlayer.duration; | ||
}, | ||
/** | ||
* Returns whether or not the current media item has finished playing. Returns `false` | ||
* if a media item has not been loaded, has not been played, or has not yet finished | ||
* playing. | ||
* | ||
* @returns {boolean} true if the current media item has finished playing | ||
* @see {@link http://docs.videojs.com/Player.html#ended} | ||
*/ | ||
ended: function() { | ||
var mediaSession = this._getMediaSession(); | ||
/** | ||
* Returns whether or not the current media item has finished playing. | ||
* Returns `false` if a media item has not been loaded, has not been played, | ||
* or has not yet finished playing. | ||
* | ||
* @returns {boolean} true if the current media item has finished playing | ||
* @see {@link http://docs.videojs.com/Player.html#ended} | ||
*/ | ||
ended() { | ||
var mediaSession = this._getMediaSession(); | ||
if (!mediaSession && this._hasMediaSessionEnded) { | ||
return true; | ||
if (!mediaSession && this._hasMediaSessionEnded) { | ||
return true; | ||
} | ||
return mediaSession ? (mediaSession.idleReason === chrome.cast.media.IdleReason.FINISHED) : false; | ||
} | ||
return mediaSession ? (mediaSession.idleReason === chrome.cast.media.IdleReason.FINISHED) : false; | ||
}, | ||
/** | ||
* Returns the current volume level setting as a decimal number between `0` and `1`. | ||
* | ||
* @returns {number} the current volume level | ||
* @see {@link http://docs.videojs.com/Player.html#volume} | ||
*/ | ||
volume() { | ||
return this._remotePlayer.volumeLevel; | ||
} | ||
/** | ||
* Returns the current volume level setting as a decimal number between `0` and `1`. | ||
* | ||
* @returns {number} the current volume level | ||
* @see {@link http://docs.videojs.com/Player.html#volume} | ||
*/ | ||
volume: function() { | ||
return this._remotePlayer.volumeLevel; | ||
}, | ||
/** | ||
* Sets the current volume level. Volume level is a decimal number | ||
* between `0` and `1`, where `0` is muted and `1` is the loudest volume level. | ||
* | ||
* @param volumeLevel {number} | ||
* @returns {number} the current volume level | ||
* @see {@link http://docs.videojs.com/Player.html#volume} | ||
*/ | ||
setVolume(volumeLevel) { | ||
this._remotePlayer.volumeLevel = volumeLevel; | ||
this._remotePlayerController.setVolumeLevel(); | ||
// This event is triggered by the listener on | ||
// `RemotePlayerEventType.VOLUME_LEVEL_CHANGED`, but waiting for | ||
// that event to fire in response to calls to `setVolume` introduces | ||
// noticeable lag in the updating of the player UI's volume slider bar, | ||
// which makes user interaction with the volume slider choppy. | ||
this._triggerVolumeChangeEvent(); | ||
} | ||
/** | ||
* Sets the current volume level. Volume level is a decimal number between `0` and `1`, | ||
* where `0` is muted and `1` is the loudest volume level. | ||
* | ||
* @param volumeLevel {number} | ||
* @returns {number} the current volume level | ||
* @see {@link http://docs.videojs.com/Player.html#volume} | ||
*/ | ||
setVolume: function(volumeLevel) { | ||
this._remotePlayer.volumeLevel = volumeLevel; | ||
this._remotePlayerController.setVolumeLevel(); | ||
// This event is triggered by the listener on | ||
// `RemotePlayerEventType.VOLUME_LEVEL_CHANGED`, but waiting for that event to fire | ||
// in response to calls to `setVolume` introduces noticeable lag in the updating of | ||
// the player UI's volume slider bar, which makes user interaction with the volume | ||
// slider choppy. | ||
this._triggerVolumeChangeEvent(); | ||
}, | ||
/** | ||
* Returns whether or not the player is currently muted. | ||
* | ||
* @returns {boolean} true if the player is currently muted | ||
* @see {@link http://docs.videojs.com/Player.html#muted} | ||
*/ | ||
muted() { | ||
return this._remotePlayer.isMuted; | ||
} | ||
/** | ||
* Returns whether or not the player is currently muted. | ||
* | ||
* @returns {boolean} true if the player is currently muted | ||
* @see {@link http://docs.videojs.com/Player.html#muted} | ||
*/ | ||
muted: function() { | ||
return this._remotePlayer.isMuted; | ||
}, | ||
/** | ||
* Mutes or un-mutes the player. Does nothing if the player is currently | ||
* muted and the `isMuted` parameter is true or if the player is not muted and | ||
* `isMuted` is false. | ||
* | ||
* @param isMuted {boolean} whether or not the player should be muted | ||
* @see {@link http://docs.videojs.com/Html5.html#setMuted} for an example | ||
*/ | ||
setMuted(isMuted) { | ||
if ((this._remotePlayer.isMuted && !isMuted) || (!this._remotePlayer.isMuted && isMuted)) { | ||
this._remotePlayerController.muteOrUnmute(); | ||
} | ||
} | ||
/** | ||
* Mutes or un-mutes the player. Does nothing if the player is currently muted and the | ||
* `isMuted` parameter is true or if the player is not muted and `isMuted` is false. | ||
* | ||
* @param isMuted {boolean} whether or not the player should be muted | ||
* @see {@link http://docs.videojs.com/Html5.html#setMuted} for an example | ||
*/ | ||
setMuted: function(isMuted) { | ||
if ((this._remotePlayer.isMuted && !isMuted) || (!this._remotePlayer.isMuted && isMuted)) { | ||
this._remotePlayerController.muteOrUnmute(); | ||
/** | ||
* Gets the URL to the current poster image. | ||
* | ||
* @returns {string} URL to the current poster image or `undefined` if none exists | ||
* @see {@link http://docs.videojs.com/Player.html#poster} | ||
*/ | ||
poster() { | ||
return this._ui.getPoster(); | ||
} | ||
}, | ||
/** | ||
* Gets the URL to the current poster image. | ||
* | ||
* @returns {string} URL to the current poster image or `undefined` if none exists | ||
* @see {@link http://docs.videojs.com/Player.html#poster} | ||
*/ | ||
poster: function() { | ||
return this._ui.getPoster(); | ||
}, | ||
/** | ||
* Sets the URL to the current poster image. The poster image shown | ||
* in the Chromecast Tech UI view is updated with this new URL. | ||
* | ||
* @param poster {string} the URL to the new poster image | ||
* @see {@link http://docs.videojs.com/Tech.html#setPoster} | ||
*/ | ||
setPoster(poster) { | ||
this._ui.updatePoster(poster); | ||
} | ||
/** | ||
* Sets the URL to the current poster image. The poster image shown in the Chromecast | ||
* Tech UI view is updated with this new URL. | ||
* | ||
* @param poster {string} the URL to the new poster image | ||
* @see {@link http://docs.videojs.com/Tech.html#setPoster} | ||
*/ | ||
setPoster: function(poster) { | ||
this._ui.updatePoster(poster); | ||
}, | ||
/** | ||
* This function is "required" when implementing {@link external:Tech} | ||
* and is supposed to return a mock | ||
* {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|TimeRanges} | ||
* object that represents the portions of the current media item that have been | ||
* buffered. However, the Chromecast API does not currently provide a way | ||
* to determine how much the media item has buffered, so we always | ||
* return `undefined`. | ||
* | ||
* Returning `undefined` is safe: the player will simply not display | ||
* the buffer amount indicator in the scrubber UI. | ||
* | ||
* @returns {undefined} always returns `undefined` | ||
* @see {@link http://docs.videojs.com/Player.html#buffered} | ||
*/ | ||
buffered() { | ||
return undefined; | ||
} | ||
/** | ||
* This function is "required" when implementing {@link external:Tech} and is supposed | ||
* to return a mock | ||
* {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|TimeRanges} | ||
* object that represents the portions of the current media item that have been | ||
* buffered. However, the Chromecast API does not currently provide a way to determine | ||
* how much the media item has buffered, so we always return `undefined`. | ||
* | ||
* Returning `undefined` is safe: the player will simply not display the buffer amount | ||
* indicator in the scrubber UI. | ||
* | ||
* @returns {undefined} always returns `undefined` | ||
* @see {@link http://docs.videojs.com/Player.html#buffered} | ||
*/ | ||
buffered: function() { | ||
return undefined; | ||
}, | ||
/** | ||
* This function is "required" when implementing {@link external:Tech} | ||
* and is supposed to return a mock | ||
* {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|TimeRanges} | ||
* object that represents the portions of the current media item that has playable | ||
* content. However, the Chromecast API does not currently provide a | ||
* way to determine how much the media item has playable content, so | ||
* we'll just assume the entire video is an available seek target. | ||
* | ||
* The risk here lies with live streaming, where there may exist a sliding window of | ||
* playable content and seeking is only possible within the last X number of | ||
* minutes, rather than for the entire video. | ||
* | ||
* Unfortunately we have no way of detecting when this is the case. Returning | ||
* anything other than the full range of the video means that we lose the ability | ||
* to seek during VOD. | ||
* | ||
* @returns {TimeRanges} always returns a `TimeRanges` object with one | ||
* `TimeRange` that starts at `0` and ends at the `duration` of the | ||
* current media item | ||
* @see {@link http://docs.videojs.com/Player.html#seekable} | ||
*/ | ||
seekable() { | ||
// TODO Investigate if there's a way to detect | ||
// if the source is live, so that we can | ||
// possibly adjust the seekable `TimeRanges` accordingly. | ||
return this.videojs.createTimeRange(0, this.duration()); | ||
} | ||
/** | ||
* This function is "required" when implementing {@link external:Tech} and is supposed | ||
* to return a mock | ||
* {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|TimeRanges} | ||
* object that represents the portions of the current media item that has playable | ||
* content. However, the Chromecast API does not currently provide a way to determine | ||
* how much the media item has playable content, so we'll just assume the entire video | ||
* is an available seek target. | ||
* | ||
* The risk here lies with live streaming, where there may exist a sliding window of | ||
* playable content and seeking is only possible within the last X number of minutes, | ||
* rather than for the entire video. | ||
* | ||
* Unfortunately we have no way of detecting when this is the case. Returning anything | ||
* other than the full range of the video means that we lose the ability to seek during | ||
* VOD. | ||
* | ||
* @returns {TimeRanges} always returns a `TimeRanges` object with one `TimeRange` that | ||
* starts at `0` and ends at the `duration` of the current media item | ||
* @see {@link http://docs.videojs.com/Player.html#seekable} | ||
*/ | ||
seekable: function() { | ||
// TODO Investigate if there's a way to detect if the source is live, so that we can | ||
// possibly adjust the seekable `TimeRanges` accordingly. | ||
return this.videojs.createTimeRange(0, this.duration()); | ||
}, | ||
/** | ||
* Returns whether the native media controls should be shown (`true`) or hidden | ||
* (`false`). Not applicable to this Tech. | ||
* | ||
* @returns {boolean} always returns `false` | ||
* @see {@link http://docs.videojs.com/Html5.html#controls} for an example | ||
*/ | ||
controls() { | ||
return false; | ||
} | ||
/** | ||
* Returns whether the native media controls should be shown (`true`) or hidden | ||
* (`false`). Not applicable to this Tech. | ||
* | ||
* @returns {boolean} always returns `false` | ||
* @see {@link http://docs.videojs.com/Html5.html#controls} for an example | ||
*/ | ||
controls: function() { | ||
return false; | ||
}, | ||
/** | ||
* Returns whether or not the browser should show the player | ||
* "inline" (non-fullscreen) by default. This function always | ||
* returns true to tell the browser that non-fullscreen playback is preferred. | ||
* | ||
* @returns {boolean} always returns `true` | ||
* @see {@link http://docs.videojs.com/Html5.html#playsinline} for an example | ||
*/ | ||
playsinline() { | ||
return true; | ||
} | ||
/** | ||
* Returns whether or not the browser should show the player "inline" (non-fullscreen) | ||
* by default. This function always returns true to tell the browser that non- | ||
* fullscreen playback is preferred. | ||
* | ||
* @returns {boolean} always returns `true` | ||
* @see {@link http://docs.videojs.com/Html5.html#playsinline} for an example | ||
*/ | ||
playsinline: function() { | ||
return true; | ||
}, | ||
/** | ||
* Returns whether or not fullscreen is supported by this Tech. | ||
* Always returns `true` because fullscreen is always supported. | ||
* | ||
* @returns {boolean} always returns `true` | ||
* @see {@link http://docs.videojs.com/Html5.html#supportsFullScreen} for an example | ||
*/ | ||
supportsFullScreen() { | ||
return true; | ||
} | ||
/** | ||
* Returns whether or not fullscreen is supported by this Tech. Always returns `true` | ||
* because fullscreen is always supported. | ||
* | ||
* @returns {boolean} always returns `true` | ||
* @see {@link http://docs.videojs.com/Html5.html#supportsFullScreen} for an example | ||
*/ | ||
supportsFullScreen: function() { | ||
return true; | ||
}, | ||
/** | ||
* Sets a flag that determines whether or not the media should automatically begin | ||
* playing on page load. This is not supported because a Chromecast session must be | ||
* initiated by casting via the casting menu and cannot autoplay. | ||
* | ||
* @see {@link http://docs.videojs.com/Html5.html#setAutoplay} for an example | ||
*/ | ||
setAutoplay() { | ||
// Not supported | ||
} | ||
/** | ||
* Sets a flag that determines whether or not the media should automatically begin | ||
* playing on page load. This is not supported because a Chromecast session must be | ||
* initiated by casting via the casting menu and cannot autoplay. | ||
* | ||
* @see {@link http://docs.videojs.com/Html5.html#setAutoplay} for an example | ||
*/ | ||
setAutoplay: function() { | ||
// Not supported | ||
}, | ||
/** | ||
* @returns {number} the chromecast player's playback rate, if available. Otherwise, | ||
* the return value defaults to `1`. | ||
*/ | ||
playbackRate() { | ||
var mediaSession = this._getMediaSession(); | ||
/** | ||
* @returns {number} the chromecast player's playback rate, if available. Otherwise, | ||
* the return value defaults to `1`. | ||
*/ | ||
playbackRate: function() { | ||
var mediaSession = this._getMediaSession(); | ||
return mediaSession ? mediaSession.playbackRate : 1; | ||
} | ||
return mediaSession ? mediaSession.playbackRate : 1; | ||
}, | ||
/** | ||
* Does nothing. Changing the playback rate is not supported. | ||
*/ | ||
setPlaybackRate() { | ||
// Not supported | ||
} | ||
/** | ||
* Does nothing. Changing the playback rate is not supported. | ||
*/ | ||
setPlaybackRate: function() { | ||
// Not supported | ||
}, | ||
/** | ||
* Does nothing. Satisfies calls to the missing preload method. | ||
*/ | ||
preload() { | ||
// Not supported | ||
} | ||
/** | ||
* Does nothing. Satisfies calls to the missing preload method. | ||
*/ | ||
preload: function() { | ||
// Not supported | ||
}, | ||
/** | ||
* Causes the Tech to begin loading the current source. `load` | ||
* is not supported in this ChromecastTech because setting the | ||
* source on the `Chromecast` automatically causes it to begin loading. | ||
*/ | ||
load() { | ||
// Not supported | ||
} | ||
/** | ||
* Causes the Tech to begin loading the current source. `load` is not supported in this | ||
* ChromecastTech because setting the source on the `Chromecast` automatically causes | ||
* it to begin loading. | ||
*/ | ||
load: function() { | ||
// Not supported | ||
}, | ||
/** | ||
* Gets the Chromecast equivalent of HTML5 Media Element's `readyState`. | ||
* | ||
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState | ||
*/ | ||
readyState: function() { | ||
if (this._remotePlayer.playerState === 'IDLE' || this._remotePlayer.playerState === 'BUFFERING') { | ||
return 0; // HAVE_NOTHING | ||
/** | ||
* Gets the Chromecast equivalent of HTML5 Media Element's `readyState`. | ||
* | ||
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState | ||
*/ | ||
readyState() { | ||
if (this._remotePlayer.playerState === 'IDLE' || this._remotePlayer.playerState === 'BUFFERING') { | ||
return 0; // HAVE_NOTHING | ||
} | ||
return 4; | ||
} | ||
return 4; | ||
}, | ||
/** | ||
* Wires up event listeners for | ||
* [RemotePlayerController](https://developers.google.com/cast/docs/reference/chrome/cast.framework.RemotePlayerController) | ||
* events. | ||
* | ||
* @private | ||
*/ | ||
_listenToPlayerControllerEvents: function() { | ||
var eventTypes = cast.framework.RemotePlayerEventType; | ||
/** | ||
* Wires up event listeners for | ||
* [RemotePlayerController](https://developers.google.com/cast/docs/reference/chrome/cast.framework.RemotePlayerController) | ||
* events. | ||
* | ||
* @private | ||
*/ | ||
_listenToPlayerControllerEvents() { | ||
var eventTypes = cast.framework.RemotePlayerEventType; | ||
this._addEventListener(this._remotePlayerController, eventTypes.PLAYER_STATE_CHANGED, this._onPlayerStateChanged, this); | ||
this._addEventListener(this._remotePlayerController, eventTypes.VOLUME_LEVEL_CHANGED, this._triggerVolumeChangeEvent, this); | ||
this._addEventListener(this._remotePlayerController, eventTypes.IS_MUTED_CHANGED, this._triggerVolumeChangeEvent, this); | ||
this._addEventListener(this._remotePlayerController, eventTypes.CURRENT_TIME_CHANGED, this._triggerTimeUpdateEvent, this); | ||
this._addEventListener(this._remotePlayerController, eventTypes.DURATION_CHANGED, this._triggerDurationChangeEvent, this); | ||
}, | ||
this._addEventListener(this._remotePlayerController, eventTypes.PLAYER_STATE_CHANGED, this._onPlayerStateChanged, this); | ||
this._addEventListener(this._remotePlayerController, eventTypes.VOLUME_LEVEL_CHANGED, this._triggerVolumeChangeEvent, this); | ||
this._addEventListener(this._remotePlayerController, eventTypes.IS_MUTED_CHANGED, this._triggerVolumeChangeEvent, this); | ||
this._addEventListener(this._remotePlayerController, eventTypes.CURRENT_TIME_CHANGED, this._triggerTimeUpdateEvent, this); | ||
this._addEventListener(this._remotePlayerController, eventTypes.DURATION_CHANGED, this._triggerDurationChangeEvent, this); | ||
} | ||
/** | ||
* Registers an event listener on the given target object. Because many objects in the | ||
* Chromecast API are either singletons or must be shared between instances of | ||
* `ChromecastTech` for the lifetime of the player, we must unbind the listeners when | ||
* this Tech instance is destroyed to prevent memory leaks. To do that, we need to keep | ||
* a reference to listeners that are added to global objects so that we can use those | ||
* references to remove the listener when this Tech is destroyed. | ||
* | ||
* @param target {object} the object to register the event listener on | ||
* @param type {string} the name of the event | ||
* @param callback {Function} the listener's callback function that executes when the | ||
* event is emitted | ||
* @param context {object} the `this` context to use when executing the `callback` | ||
* @private | ||
*/ | ||
_addEventListener: function(target, type, callback, context) { | ||
var listener; | ||
/** | ||
* Registers an event listener on the given target object. | ||
* Because many objects in the Chromecast API are either singletons | ||
* or must be shared between instances of `ChromecastTech` for the | ||
* lifetime of the player, we must unbind the listeners when this Tech | ||
* instance is destroyed to prevent memory leaks. To do that, we need to keep | ||
* a reference to listeners that are added to global objects so that we can | ||
* use those references to remove the listener when this Tech is destroyed. | ||
* | ||
* @param target {object} the object to register the event listener on | ||
* @param type {string} the name of the event | ||
* @param callback {Function} the listener's callback function that | ||
* executes when the event is emitted | ||
* @param context {object} the `this` context to use when executing the `callback` | ||
* @private | ||
*/ | ||
_addEventListener(target, type, callback, context) { | ||
var listener; | ||
listener = { | ||
target: target, | ||
type: type, | ||
callback: callback, | ||
context: context, | ||
listener: callback.bind(context), | ||
}; | ||
target.addEventListener(type, listener.listener); | ||
this._eventListeners.push(listener); | ||
}, | ||
listener = { | ||
target: target, | ||
type: type, | ||
callback: callback, | ||
context: context, | ||
listener: callback.bind(context), | ||
}; | ||
target.addEventListener(type, listener.listener); | ||
this._eventListeners.push(listener); | ||
} | ||
/** | ||
* Removes all event listeners that were registered with global objects during the | ||
* lifetime of this Tech. See {@link _addEventListener} for more information about why | ||
* this is necessary. | ||
* | ||
* @private | ||
*/ | ||
_removeAllEventListeners: function() { | ||
while (this._eventListeners.length > 0) { | ||
this._removeEventListener(this._eventListeners[0]); | ||
/** | ||
* Removes all event listeners that were registered with global objects during the | ||
* lifetime of this Tech. See {@link _addEventListener} for more information | ||
* about why this is necessary. | ||
* | ||
* @private | ||
*/ | ||
_removeAllEventListeners() { | ||
while (this._eventListeners.length > 0) { | ||
this._removeEventListener(this._eventListeners[0]); | ||
} | ||
this._eventListeners = []; | ||
} | ||
this._eventListeners = []; | ||
}, | ||
/** | ||
* Removes a single event listener that was registered with global objects during the | ||
* lifetime of this Tech. See {@link _addEventListener} for more information about why | ||
* this is necessary. | ||
* | ||
* @private | ||
*/ | ||
_removeEventListener: function(listener) { | ||
var index = -1, | ||
pass = false, | ||
i; | ||
/** | ||
* Removes a single event listener that was registered with global objects | ||
* during the lifetime of this Tech. See {@link _addEventListener} for | ||
* more information about why this is necessary. | ||
* | ||
* @private | ||
*/ | ||
_removeEventListener(listener) { | ||
var index = -1, | ||
pass = false, | ||
i; | ||
listener.target.removeEventListener(listener.type, listener.listener); | ||
listener.target.removeEventListener(listener.type, listener.listener); | ||
for (i = 0; i < this._eventListeners.length; i++) { | ||
pass = this._eventListeners[i].target === listener.target && | ||
this._eventListeners[i].type === listener.type && | ||
this._eventListeners[i].callback === listener.callback && | ||
this._eventListeners[i].context === listener.context; | ||
for (i = 0; i < this._eventListeners.length; i++) { | ||
pass = this._eventListeners[i].target === listener.target && | ||
this._eventListeners[i].type === listener.type && | ||
this._eventListeners[i].callback === listener.callback && | ||
this._eventListeners[i].context === listener.context; | ||
if (pass) { | ||
index = i; | ||
break; | ||
if (pass) { | ||
index = i; | ||
break; | ||
} | ||
} | ||
} | ||
if (index !== -1) { | ||
this._eventListeners.splice(index, 1); | ||
if (index !== -1) { | ||
this._eventListeners.splice(index, 1); | ||
} | ||
} | ||
}, | ||
/** | ||
* Handles Chromecast player state change events. The player may "change state" when | ||
* paused, played, buffering, etc. | ||
* | ||
* @private | ||
*/ | ||
_onPlayerStateChanged: function() { | ||
var states = chrome.cast.media.PlayerState, | ||
playerState = this._remotePlayer.playerState; | ||
/** | ||
* Handles Chromecast player state change events. The player may "change state" when | ||
* paused, played, buffering, etc. | ||
* | ||
* @private | ||
*/ | ||
_onPlayerStateChanged() { | ||
var states = chrome.cast.media.PlayerState, | ||
playerState = this._remotePlayer.playerState; | ||
if (playerState === states.PLAYING) { | ||
this._hasPlayedCurrentItem = true; | ||
this.trigger('play'); | ||
this.trigger('playing'); | ||
} else if (playerState === states.PAUSED) { | ||
this.trigger('pause'); | ||
} else if ((playerState === states.IDLE && this.ended()) || (playerState === null && this._hasPlayedCurrentItem)) { | ||
this._hasPlayedCurrentItem = false; | ||
this._closeSessionOnTimeout(); | ||
this.trigger('ended'); | ||
this._triggerTimeUpdateEvent(); | ||
} else if (playerState === states.BUFFERING) { | ||
this.trigger('waiting'); | ||
if (playerState === states.PLAYING) { | ||
this._hasPlayedCurrentItem = true; | ||
this.trigger('play'); | ||
this.trigger('playing'); | ||
} else if (playerState === states.PAUSED) { | ||
this.trigger('pause'); | ||
} else if ((playerState === states.IDLE && this.ended()) || (playerState === null && this._hasPlayedCurrentItem)) { | ||
this._hasPlayedCurrentItem = false; | ||
this._closeSessionOnTimeout(); | ||
this.trigger('ended'); | ||
this._triggerTimeUpdateEvent(); | ||
} else if (playerState === states.BUFFERING) { | ||
this.trigger('waiting'); | ||
} | ||
} | ||
}, | ||
/** | ||
* Handles Chromecast MediaSession state change events. The only property sent to this | ||
* event is whether the session is alive. This is useful for determining if an item has | ||
* ended as the MediaSession will fire this event with `false` then be immediately | ||
* destroyed. This means that we cannot trust `idleReason` to show whether an item has | ||
* ended since we may no longer have access to the MediaSession. | ||
* | ||
* @private | ||
*/ | ||
_onMediaSessionStatusChanged: function(isAlive) { | ||
this._hasMediaSessionEnded = !!isAlive; | ||
}, | ||
/** | ||
* Handles Chromecast MediaSession state change events. The only property sent | ||
* to this event is whether the session is alive. This is useful for determining | ||
* if an item has ended as the MediaSession will fire this event with `false` then | ||
* be immediately destroyed. This means that we cannot trust `idleReason` to show | ||
* whether an item has ended since we may no longer have access to the MediaSession. | ||
* | ||
* @private | ||
*/ | ||
_onMediaSessionStatusChanged(isAlive) { | ||
this._hasMediaSessionEnded = !!isAlive; | ||
} | ||
/** | ||
* Ends the session after a certain number of seconds of inactivity. | ||
* | ||
* If the Chromecast player is in the "IDLE" state after an item has ended, and no | ||
* further items are queued up to play, the session is considered inactive. Once a | ||
* period of time (currently 10 seconds) has elapsed with no activity, we manually end | ||
* the session to prevent long periods of a blank Chromecast screen that is shown at | ||
* the end of item playback. | ||
* | ||
* @private | ||
*/ | ||
_closeSessionOnTimeout: function() { | ||
// Ensure that there's never more than one session timeout active | ||
this._clearSessionTimeout(); | ||
this._sessionTimeoutID = setTimeout(function() { | ||
var castSession = this._getCastSession(); | ||
if (castSession) { | ||
castSession.endSession(true); | ||
} | ||
/** | ||
* Ends the session after a certain number of seconds of inactivity. | ||
* | ||
* If the Chromecast player is in the "IDLE" state after an item has ended, and no | ||
* further items are queued up to play, the session is considered inactive. Once a | ||
* period of time (currently 10 seconds) has elapsed with no activity, we manually | ||
* end the session to prevent long periods of a blank Chromecast screen that is | ||
* shown at the end of item playback. | ||
* | ||
* @private | ||
*/ | ||
_closeSessionOnTimeout() { | ||
// Ensure that there's never more than one session timeout active | ||
this._clearSessionTimeout(); | ||
}.bind(this), SESSION_TIMEOUT); | ||
}, | ||
this._sessionTimeoutID = setTimeout(function() { | ||
var castSession = this._getCastSession(); | ||
/** | ||
* Stops the timeout that is waiting during a period of inactivity in order to close | ||
* the session. | ||
* | ||
* @private | ||
* @see _closeSessionOnTimeout | ||
*/ | ||
_clearSessionTimeout: function() { | ||
if (this._sessionTimeoutID) { | ||
clearTimeout(this._sessionTimeoutID); | ||
this._sessionTimeoutID = false; | ||
if (castSession) { | ||
castSession.endSession(true); | ||
} | ||
this._clearSessionTimeout(); | ||
}.bind(this), SESSION_TIMEOUT); | ||
} | ||
}, | ||
/** | ||
* @private | ||
* @return {object} the current CastContext, if one exists | ||
*/ | ||
_getCastContext: function() { | ||
return this._chromecastSessionManager.getCastContext(); | ||
}, | ||
/** | ||
* Stops the timeout that is waiting during a period of inactivity in order to close | ||
* the session. | ||
* | ||
* @private | ||
* @see _closeSessionOnTimeout | ||
*/ | ||
_clearSessionTimeout() { | ||
if (this._sessionTimeoutID) { | ||
clearTimeout(this._sessionTimeoutID); | ||
this._sessionTimeoutID = false; | ||
} | ||
} | ||
/** | ||
* @private | ||
* @return {object} the current CastSession, if one exists | ||
*/ | ||
_getCastSession: function() { | ||
return this._getCastContext().getCurrentSession(); | ||
}, | ||
/** | ||
* @private | ||
* @return {object} the current CastContext, if one exists | ||
*/ | ||
_getCastContext() { | ||
return this._chromecastSessionManager.getCastContext(); | ||
} | ||
/** | ||
* @private | ||
* @return {object} the current MediaSession, if one exists | ||
* @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media | ||
*/ | ||
_getMediaSession: function() { | ||
var castSession = this._getCastSession(); | ||
/** | ||
* @private | ||
* @return {object} the current CastSession, if one exists | ||
*/ | ||
_getCastSession() { | ||
return this._getCastContext().getCurrentSession(); | ||
} | ||
return castSession ? castSession.getMediaSession() : null; | ||
}, | ||
/** | ||
* @private | ||
* @return {object} the current MediaSession, if one exists | ||
* @see https://developers.google.com/cast/docs/reference/chrome/chrome.cast.media.Media | ||
*/ | ||
_getMediaSession() { | ||
var castSession = this._getCastSession(); | ||
/** | ||
* Triggers a 'volumechange' event | ||
* @private | ||
* @see http://docs.videojs.com/Player.html#event:volumechange | ||
*/ | ||
_triggerVolumeChangeEvent: function() { | ||
this.trigger('volumechange'); | ||
}, | ||
return castSession ? castSession.getMediaSession() : null; | ||
} | ||
/** | ||
* Triggers a 'timeupdate' event | ||
* @private | ||
* @see http://docs.videojs.com/Player.html#event:timeupdate | ||
*/ | ||
_triggerTimeUpdateEvent: function() { | ||
this.trigger('timeupdate'); | ||
}, | ||
/** | ||
* Triggers a 'volumechange' event | ||
* @private | ||
* @see http://docs.videojs.com/Player.html#event:volumechange | ||
*/ | ||
_triggerVolumeChangeEvent() { | ||
this.trigger('volumechange'); | ||
} | ||
/** | ||
* Triggers a 'durationchange' event | ||
* @private | ||
* @see http://docs.videojs.com/Player.html#event:durationchange | ||
*/ | ||
_triggerDurationChangeEvent: function() { | ||
this.trigger('durationchange'); | ||
}, | ||
/** | ||
* Triggers a 'timeupdate' event | ||
* @private | ||
* @see http://docs.videojs.com/Player.html#event:timeupdate | ||
*/ | ||
_triggerTimeUpdateEvent() { | ||
this.trigger('timeupdate'); | ||
} | ||
/** | ||
* Triggers an 'error' event | ||
* @private | ||
* @see http://docs.videojs.com/Player.html#event:error | ||
*/ | ||
_triggerErrorEvent: function() { | ||
this.trigger('error'); | ||
}, | ||
}; | ||
/** | ||
* Triggers a 'durationchange' event | ||
* @private | ||
* @see http://docs.videojs.com/Player.html#event:durationchange | ||
*/ | ||
_triggerDurationChangeEvent() { | ||
this.trigger('durationchange'); | ||
} | ||
/** | ||
* Registers the ChromecastTech Tech with Video.js. Calls {@link | ||
* http://docs.videojs.com/Tech.html#.registerTech}, which will add a Tech called | ||
* `chromecast` to the list of globally registered Video.js Tech implementations. | ||
* | ||
* [Video.js Tech](http://docs.videojs.com/Tech.html) are initialized and used | ||
* automatically by Video.js Player instances. Whenever a new source is set on the player, | ||
* the player iterates through the list of available Tech to determine which to use to | ||
* play the source. | ||
* | ||
* @param videojs {object} A reference to | ||
* {@link http://docs.videojs.com/module-videojs.html|Video.js} | ||
* @see http://docs.videojs.com/Tech.html#.registerTech | ||
*/ | ||
module.exports = function(videojs) { | ||
var Tech = videojs.getComponent('Tech'), | ||
ChromecastTechImpl; | ||
/** | ||
* Triggers an 'error' event | ||
* @private | ||
* @see http://docs.videojs.com/Player.html#event:error | ||
*/ | ||
_triggerErrorEvent() { | ||
this.trigger('error'); | ||
} | ||
} | ||
ChromecastTechImpl = videojs.extend(Tech, ChromecastTech); | ||
// Required for Video.js Tech implementations. | ||
// TODO Consider a more comprehensive check based on mimetype. | ||
ChromecastTechImpl.canPlaySource = () => { return ChromecastSessionManager.isChromecastConnected(); }; | ||
ChromecastTechImpl.isSupported = () => { return ChromecastSessionManager.isChromecastConnected(); }; | ||
ChromecastTech.canPlaySource = () => { | ||
return ChromecastSessionManager.isChromecastConnected(); | ||
}; | ||
ChromecastTechImpl.prototype.featuresVolumeControl = true; | ||
ChromecastTechImpl.prototype.featuresPlaybackRate = false; | ||
ChromecastTechImpl.prototype.movingMediaElementInDOM = false; | ||
ChromecastTechImpl.prototype.featuresFullscreenResize = true; | ||
ChromecastTechImpl.prototype.featuresTimeupdateEvents = true; | ||
ChromecastTechImpl.prototype.featuresProgressEvents = false; | ||
// Text tracks are not supported in this version | ||
ChromecastTechImpl.prototype.featuresNativeTextTracks = false; | ||
ChromecastTechImpl.prototype.featuresNativeAudioTracks = false; | ||
ChromecastTechImpl.prototype.featuresNativeVideoTracks = false; | ||
ChromecastTech.isSupported = () => { | ||
return ChromecastSessionManager.isChromecastConnected(); | ||
}; | ||
// Give ChromecastTech class instances a reference to videojs | ||
ChromecastTechImpl.prototype.videojs = videojs; | ||
videojs.registerTech('chromecast', ChromecastTechImpl); | ||
videojs.registerTech('chromecast', ChromecastTech); | ||
}; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
649752
13963
4