Chromium Code Reviews| 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..70f355893fdbdcc31c289177b19486480f0f7cff 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 |
| @@ -4,6 +4,9 @@ |
| 'use strict'; |
| +// Inverval for updating media info (in ms). |
|
hirono
2014/07/24 02:12:36
nit: jsdoc @const, @type
yoshiki
2014/07/24 02:57:32
Done.
|
| +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 |
| @@ -11,11 +14,19 @@ |
| * |
| * @constructor |
|
hirono
2014/07/24 02:12:36
nit: @param
yoshiki
2014/07/24 02:57:32
Done.
|
| */ |
| -function CastVideoElement() { |
| - this.duration_ = null; |
| +function CastVideoElement(mediaInfo, session) { |
| + this.mediaInfo_ = mediaInfo; |
| + this.castMedia_ = null; |
| + this.castSession_ = session; |
| + this.lastDuration_ = null; |
| this.currentTime_ = null; |
| this.src_ = ''; |
| this.volume_ = 100; |
| + this.currentMediaPlayerState_ = null; |
| + this.currentMediaCurrentTime_ = null; |
| + this.currentMediaDuration_ = null; |
| + |
| + this.onCastMediaUpdatedBound_ = this.onCastMediaUpdated_.bind(this); |
| } |
| CastVideoElement.prototype = { |
| @@ -34,7 +45,12 @@ CastVideoElement.prototype = { |
| * @type {number} |
|
hirono
2014/07/24 02:12:36
nit: @return
Same for the followings.
yoshiki
2014/07/24 02:57:31
I think we don't need it because it's a getter met
hirono
2014/07/24 03:42:49
nit: So please just make #37 consistent with other
yoshiki
2014/07/24 05:19:00
Done.
|
| */ |
| get duration() { |
| - return this.duration_; |
| + if (this.castMedia_ && this.castMedia_.media) { |
| + this.lastDuration_ = this.castMedia_.media.duration; |
|
hirono
2014/07/24 02:12:36
Changing a value in a getter is a bit strange.
Can
yoshiki
2014/07/24 02:57:31
Done.
|
| + return this.castMedia_.media.duration; |
| + } |
| + |
| + return this.lastDuration_; |
| }, |
| /** |
| @@ -42,10 +58,10 @@ CastVideoElement.prototype = { |
| * @type {number} |
|
hirono
2014/07/24 02:12:36
@return {?number} ?
yoshiki
2014/07/24 02:57:32
Done.
|
| */ |
| get currentTime() { |
| - return this.currentTime_; |
| + return this.castMedia_ ? this.castMedia_.getEstimatedTime() : null; |
| }, |
| set currentTime(currentTime) { |
| - this.currentTime_ = currentTime; |
| + // TODO(yoshiki): Support seek. |
|
hirono
2014/07/24 02:12:36
How about adding throw new Error('Not implemented'
yoshiki
2014/07/24 02:57:32
Please leave it as it is, since this setter is cal
hirono
2014/07/24 03:42:49
SGTM.
|
| }, |
| /** |
| @@ -53,7 +69,10 @@ CastVideoElement.prototype = { |
| * @type {boolean} |
| */ |
| get paused() { |
| - return false; |
| + if (!this.castMedia_) |
| + return false; |
| + |
| + return this.castMedia_.playerState == chrome.cast.media.PlayerState.PAUSED; |
|
hirono
2014/07/24 02:12:36
=== ? Same for others.
yoshiki
2014/07/24 02:57:31
Done.
|
| }, |
| /** |
| @@ -61,7 +80,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 +91,7 @@ CastVideoElement.prototype = { |
| * @type {boolean} |
| */ |
| get seekable() { |
| + // TODO(yoshiki): Support seek. |
| return false; |
| }, |
| @@ -77,38 +100,186 @@ 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} |
| + * @return {null} |
|
hirono
2014/07/24 02:12:36
Maybe string is OK because it's nullable.
yoshiki
2014/07/24 02:57:32
changed to '?string'
hirono
2014/07/24 03:42:49
Yes, I misunderstood.
|
| */ |
| 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.castMedia_.pause(null, |
| + function () {}, |
| + 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) { |
| + this.castMedia_ = media; |
|
hirono
2014/07/24 02:12:36
Should we check this.castMedia_ == null since it m
yoshiki
2014/07/24 02:57:32
Done.
|
| + 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; |
| + } |
| }, |
| }; |