Index: ui/file_manager/video_player/js/cast/cast_video_element.js |
diff --git a/ui/file_manager/video_player/js/cast/cast_video_element.js b/ui/file_manager/video_player/js/cast/cast_video_element.js |
index 299fa2bb5d3f1c3740861a3e1ff6379eae9091b6..5e4bb3b947692cf6b6d062ec374a750816681f4e 100644 |
--- a/ui/file_manager/video_player/js/cast/cast_video_element.js |
+++ b/ui/file_manager/video_player/js/cast/cast_video_element.js |
@@ -5,17 +5,34 @@ |
'use strict'; |
/** |
+ * Inverval for updating media info (in ms). |
+ * @type {number} |
+ * @const |
+ */ |
+var MEDIA_UPDATE_INTERVAL = 250; |
+ |
+/** |
* This class is the dummy class which has same interface as VideoElement. This |
* behaves like VideoElement, and is used for making Chromecast player |
* controlled instead of the true Video Element tag. |
* |
+ * @param {chrome.cast.media.MediaInfo} mediaInfo Data of the media to play. |
+ * @param {chrome.cast.Session} session Session to play a video on. |
* @constructor |
*/ |
-function CastVideoElement() { |
- this.duration_ = null; |
+function CastVideoElement(mediaInfo, session) { |
+ this.mediaInfo_ = mediaInfo; |
+ this.castMedia_ = null; |
+ this.castSession_ = session; |
this.currentTime_ = null; |
this.src_ = ''; |
this.volume_ = 100; |
+ this.currentMediaPlayerState_ = null; |
+ this.currentMediaCurrentTime_ = null; |
+ this.currentMediaDuration_ = null; |
+ this.pausing_ = false; |
+ |
+ this.onCastMediaUpdatedBound_ = this.onCastMediaUpdated_.bind(this); |
} |
CastVideoElement.prototype = { |
@@ -23,29 +40,29 @@ CastVideoElement.prototype = { |
/** |
* Returns a parent node. This must always be null. |
- * @return {Element} |
+ * @type {Element} |
*/ |
get parentNode() { |
return null; |
}, |
/** |
- * The total time of the video. |
- * @type {number} |
+ * The total time of the video (in sec). |
+ * @type {?number} |
*/ |
get duration() { |
- return this.duration_; |
+ return this.currentMediaDuration_; |
}, |
/** |
- * The current timestamp of the video. |
- * @type {number} |
+ * The current timestamp of the video (in sec). |
+ * @type {?number} |
*/ |
get currentTime() { |
- return this.currentTime_; |
+ return this.castMedia_ ? this.castMedia_.getEstimatedTime() : null; |
}, |
set currentTime(currentTime) { |
- this.currentTime_ = currentTime; |
+ // TODO(yoshiki): Support seek. |
}, |
/** |
@@ -53,7 +70,11 @@ CastVideoElement.prototype = { |
* @type {boolean} |
*/ |
get paused() { |
- return false; |
+ if (!this.castMedia_) |
+ return false; |
+ |
+ return this.pausing_ || |
+ this.castMedia_.playerState === chrome.cast.media.PlayerState.PAUSED; |
}, |
/** |
@@ -61,7 +82,10 @@ CastVideoElement.prototype = { |
* @type {boolean} |
*/ |
get ended() { |
- return false; |
+ if (!this.castMedia_) |
+ return true; |
+ |
+ return this.castMedia_.idleReason === chrome.cast.media.IdleReason.FINISHED; |
}, |
/** |
@@ -69,6 +93,7 @@ CastVideoElement.prototype = { |
* @type {boolean} |
*/ |
get seekable() { |
+ // TODO(yoshiki): Support seek. |
return false; |
}, |
@@ -77,38 +102,197 @@ CastVideoElement.prototype = { |
* @type {number} |
*/ |
get volume() { |
- return this.volume_; |
+ return this.castSession_.receiver.volume.muted ? |
+ 0 : |
+ this.castSession_.receiver.volume.level; |
}, |
set volume(volume) { |
- this.volume_ = volume; |
+ var VOLUME_EPS = 0.01; // Threshold for ignoring a small change. |
+ |
+ // Ignores < 1% change. |
+ if (Math.abs(this.castSession_.receiver.volume.level - volume) < VOLUME_EPS) |
+ return; |
+ |
+ if (this.castSession_.receiver.volume.muted) { |
+ if (volume < VOLUME_EPS) |
+ return; |
+ |
+ // Unmute before setting volume. |
+ this.castSession_.setReceiverMuted(false, |
+ function() {}, |
+ this.onCastCommandError_.wrap(this)); |
+ |
+ this.castSession_.setReceiverVolumeLevel(volume, |
+ function() {}, |
+ this.onCastCommandError_.wrap(this)); |
+ } else { |
+ if (volume < VOLUME_EPS) { |
+ this.castSession_.setReceiverMuted(true, |
+ function() {}, |
+ this.onCastCommandError_.wrap(this)); |
+ return; |
+ } |
+ |
+ this.castSession_.setReceiverVolumeLevel(volume, |
+ function() {}, |
+ this.onCastCommandError_.wrap(this)); |
+ } |
}, |
/** |
* Returns the source of the current video. |
- * @return {string} |
+ * @type {?string} |
*/ |
get src() { |
- return this.src_; |
+ return null; |
}, |
/** |
* Plays the video. |
*/ |
play: function() { |
- // TODO(yoshiki): Implement this. |
+ if (!this.castMedia_) { |
+ this.load(function() { |
+ this.castMedia_.play(null, |
+ function () {}, |
+ this.onCastCommandError_.wrap(this)); |
+ }.wrap(this)); |
+ return; |
+ } |
+ |
+ this.castMedia_.play(null, |
+ function () {}, |
+ this.onCastCommandError_.wrap(this)); |
}, |
/** |
* Pauses the video. |
*/ |
pause: function() { |
- // TODO(yoshiki): Implement this. |
+ if (!this.castMedia_) |
+ return; |
+ |
+ this.pausing_ = true; |
+ this.castMedia_.pause(null, |
+ function () { |
+ this.pausing_ = false; |
+ }.wrap(this), |
+ function () { |
+ this.pausing_ = false; |
+ this.onCastCommandError_(); |
+ }.wrap(this)); |
}, |
/** |
* Loads the video. |
*/ |
- load: function() { |
- // TODO(yoshiki): Implement this. |
+ load: function(opt_callback) { |
+ var request = new chrome.cast.media.LoadRequest(this.mediaInfo_); |
+ this.castSession_.loadMedia(request, |
+ function(media) { |
+ this.onMediaDiscovered_(media); |
+ if (opt_callback) |
+ opt_callback(); |
+ }.bind(this), |
+ this.onCastCommandError_.wrap(this)); |
+ }, |
+ |
+ /** |
+ * Unloads the video. |
+ * @private |
+ */ |
+ unloadMedia_: function() { |
+ this.castMedia_.removeUpdateListener(this.onCastMediaUpdatedBound_); |
+ this.castMedia_ = null; |
+ clearInterval(this.updateTimerId_); |
+ }, |
+ |
+ /** |
+ * This method is called periodically to update media information while the |
+ * media is loaded. |
+ * @private |
+ */ |
+ onPeriodicalUpdateTimer_: function() { |
+ if (!this.castMedia_) |
+ return; |
+ |
+ if (this.castMedia_.playerState === chrome.cast.media.PlayerState.PLAYING) |
+ this.onCastMediaUpdated_(true); |
+ }, |
+ |
+ /** |
+ * This method should be called when a media file is loaded. |
+ * @param {chrome.cast.Media} media Media object which was discovered. |
+ * @private |
+ */ |
+ onMediaDiscovered_: function(media) { |
+ if (this.castMedia_ !== null) { |
+ this.unloadMedia_(); |
+ console.info('New media is found and the old media is overridden.'); |
+ } |
+ |
+ this.castMedia_ = media; |
+ this.onCastMediaUpdated_(true); |
+ media.addUpdateListener(this.onCastMediaUpdatedBound_); |
+ this.updateTimerId_ = setInterval(this.onPeriodicalUpdateTimer_.bind(this), |
+ MEDIA_UPDATE_INTERVAL); |
+ }, |
+ |
+ /** |
+ * This method should be called when a media command to cast is failed. |
+ * @private |
+ */ |
+ onCastCommandError_: function() { |
+ this.unloadMedia_(); |
+ this.dispatchEvent(new Event('error')); |
+ }, |
+ |
+ /** |
+ * This is called when any media data is updated and by the periodical timer |
+ * is fired. |
+ * |
+ * @param {boolean} alive Media availability. False if it's unavailable. |
+ * @private |
+ */ |
+ onCastMediaUpdated_: function(alive) { |
+ if (!this.castMedia_) |
+ return; |
+ |
+ var media = this.castMedia_; |
+ if (this.currentMediaPlayerState_ !== media.playerState) { |
+ var oldPlayState = false; |
+ var oldState = this.currentMediaPlayerState_; |
+ if (oldState === chrome.cast.media.PlayerState.BUFFERING || |
+ oldState === chrome.cast.media.PlayerState.PLAYING) { |
+ oldPlayState = true; |
+ } |
+ var newPlayState = false; |
+ var newState = media.playerState; |
+ if (newState === chrome.cast.media.PlayerState.BUFFERING || |
+ newState === chrome.cast.media.PlayerState.PLAYING) { |
+ newPlayState = true; |
+ } |
+ if (!oldPlayState && newPlayState) |
+ this.dispatchEvent(new Event('play')); |
+ if (oldPlayState && !newPlayState) |
+ this.dispatchEvent(new Event('pause')); |
+ |
+ this.currentMediaPlayerState_ = newState; |
+ } |
+ if (this.currentMediaCurrentTime_ !== media.getEstimatedTime()) { |
+ this.dispatchEvent(new Event('timeupdate')); |
+ this.currentMediaCurrentTime_ = media.getEstimatedTime(); |
+ } |
+ |
+ if (this.currentMediaDuration_ !== media.media.duration) { |
+ this.dispatchEvent(new Event('durationchange')); |
+ this.currentMediaDuration_ = media.media.duration; |
+ } |
+ |
+ // Media is being unloaded. |
+ if (!alive) { |
+ this.unloadMedia_(); |
+ return; |
+ } |
}, |
}; |