OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 'use strict'; | 5 'use strict'; |
6 | 6 |
7 // 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.
| |
8 var MEDIA_UPDATE_INTERVAL = 250; | |
9 | |
7 /** | 10 /** |
8 * This class is the dummy class which has same interface as VideoElement. This | 11 * This class is the dummy class which has same interface as VideoElement. This |
9 * behaves like VideoElement, and is used for making Chromecast player | 12 * behaves like VideoElement, and is used for making Chromecast player |
10 * controlled instead of the true Video Element tag. | 13 * controlled instead of the true Video Element tag. |
11 * | 14 * |
12 * @constructor | 15 * @constructor |
hirono
2014/07/24 02:12:36
nit: @param
yoshiki
2014/07/24 02:57:32
Done.
| |
13 */ | 16 */ |
14 function CastVideoElement() { | 17 function CastVideoElement(mediaInfo, session) { |
15 this.duration_ = null; | 18 this.mediaInfo_ = mediaInfo; |
19 this.castMedia_ = null; | |
20 this.castSession_ = session; | |
21 this.lastDuration_ = null; | |
16 this.currentTime_ = null; | 22 this.currentTime_ = null; |
17 this.src_ = ''; | 23 this.src_ = ''; |
18 this.volume_ = 100; | 24 this.volume_ = 100; |
25 this.currentMediaPlayerState_ = null; | |
26 this.currentMediaCurrentTime_ = null; | |
27 this.currentMediaDuration_ = null; | |
28 | |
29 this.onCastMediaUpdatedBound_ = this.onCastMediaUpdated_.bind(this); | |
19 } | 30 } |
20 | 31 |
21 CastVideoElement.prototype = { | 32 CastVideoElement.prototype = { |
22 __proto__: cr.EventTarget.prototype, | 33 __proto__: cr.EventTarget.prototype, |
23 | 34 |
24 /** | 35 /** |
25 * Returns a parent node. This must always be null. | 36 * Returns a parent node. This must always be null. |
26 * @return {Element} | 37 * @return {Element} |
27 */ | 38 */ |
28 get parentNode() { | 39 get parentNode() { |
29 return null; | 40 return null; |
30 }, | 41 }, |
31 | 42 |
32 /** | 43 /** |
33 * The total time of the video. | 44 * The total time of the video. |
hirono
2014/07/24 02:12:36
Comment about the unit of time is useful.
yoshiki
2014/07/24 02:57:32
Done.
| |
34 * @type {number} | 45 * @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.
| |
35 */ | 46 */ |
36 get duration() { | 47 get duration() { |
37 return this.duration_; | 48 if (this.castMedia_ && this.castMedia_.media) { |
49 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.
| |
50 return this.castMedia_.media.duration; | |
51 } | |
52 | |
53 return this.lastDuration_; | |
38 }, | 54 }, |
39 | 55 |
40 /** | 56 /** |
41 * The current timestamp of the video. | 57 * The current timestamp of the video. |
42 * @type {number} | 58 * @type {number} |
hirono
2014/07/24 02:12:36
@return {?number} ?
yoshiki
2014/07/24 02:57:32
Done.
| |
43 */ | 59 */ |
44 get currentTime() { | 60 get currentTime() { |
45 return this.currentTime_; | 61 return this.castMedia_ ? this.castMedia_.getEstimatedTime() : null; |
46 }, | 62 }, |
47 set currentTime(currentTime) { | 63 set currentTime(currentTime) { |
48 this.currentTime_ = currentTime; | 64 // 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.
| |
49 }, | 65 }, |
50 | 66 |
51 /** | 67 /** |
52 * If this video is pauses or not. | 68 * If this video is pauses or not. |
53 * @type {boolean} | 69 * @type {boolean} |
54 */ | 70 */ |
55 get paused() { | 71 get paused() { |
56 return false; | 72 if (!this.castMedia_) |
73 return false; | |
74 | |
75 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.
| |
57 }, | 76 }, |
58 | 77 |
59 /** | 78 /** |
60 * If this video is ended or not. | 79 * If this video is ended or not. |
61 * @type {boolean} | 80 * @type {boolean} |
62 */ | 81 */ |
63 get ended() { | 82 get ended() { |
64 return false; | 83 if (!this.castMedia_) |
84 return true; | |
85 | |
86 return this.castMedia_.idleReason == chrome.cast.media.IdleReason.FINISHED; | |
65 }, | 87 }, |
66 | 88 |
67 /** | 89 /** |
68 * If this video is seelable or not. | 90 * If this video is seelable or not. |
69 * @type {boolean} | 91 * @type {boolean} |
70 */ | 92 */ |
71 get seekable() { | 93 get seekable() { |
94 // TODO(yoshiki): Support seek. | |
72 return false; | 95 return false; |
73 }, | 96 }, |
74 | 97 |
75 /** | 98 /** |
76 * Value of the volume | 99 * Value of the volume |
77 * @type {number} | 100 * @type {number} |
78 */ | 101 */ |
79 get volume() { | 102 get volume() { |
80 return this.volume_; | 103 return this.castSession_.receiver.volume.muted ? |
104 0 : | |
105 this.castSession_.receiver.volume.level; | |
81 }, | 106 }, |
82 set volume(volume) { | 107 set volume(volume) { |
83 this.volume_ = volume; | 108 var VOLUME_EPS = 0.01; // Threshold for ignoring a small change. |
109 | |
110 // Ignores < 1% change. | |
111 if (Math.abs(this.castSession_.receiver.volume.level - volume) < VOLUME_EPS) | |
112 return; | |
113 | |
114 if (this.castSession_.receiver.volume.muted) { | |
115 if (volume < VOLUME_EPS) | |
116 return; | |
117 | |
118 // Unmute before setting volume. | |
119 this.castSession_.setReceiverMuted(false, | |
120 function() {}, | |
121 this.onCastCommandError_.wrap(this)); | |
122 | |
123 this.castSession_.setReceiverVolumeLevel(volume, | |
124 function() {}, | |
125 this.onCastCommandError_.wrap(this)); | |
126 } else { | |
127 if (volume < VOLUME_EPS) { | |
128 this.castSession_.setReceiverMuted(true, | |
129 function() {}, | |
130 this.onCastCommandError_.wrap(this)); | |
131 return; | |
132 } | |
133 | |
134 this.castSession_.setReceiverVolumeLevel(volume, | |
135 function() {}, | |
136 this.onCastCommandError_.wrap(this)); | |
137 } | |
84 }, | 138 }, |
85 | 139 |
86 /** | 140 /** |
87 * Returns the source of the current video. | 141 * Returns the source of the current video. |
88 * @return {string} | 142 * @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.
| |
89 */ | 143 */ |
90 get src() { | 144 get src() { |
91 return this.src_; | 145 return null; |
92 }, | 146 }, |
93 | 147 |
94 /** | 148 /** |
95 * Plays the video. | 149 * Plays the video. |
96 */ | 150 */ |
97 play: function() { | 151 play: function() { |
98 // TODO(yoshiki): Implement this. | 152 if (!this.castMedia_) { |
153 this.load(function() { | |
154 this.castMedia_.play(null, | |
155 function () {}, | |
156 this.onCastCommandError_.wrap(this)); | |
157 }.wrap(this)); | |
158 return; | |
159 } | |
160 | |
161 this.castMedia_.play(null, | |
162 function () {}, | |
163 this.onCastCommandError_.wrap(this)); | |
99 }, | 164 }, |
100 | 165 |
101 /** | 166 /** |
102 * Pauses the video. | 167 * Pauses the video. |
103 */ | 168 */ |
104 pause: function() { | 169 pause: function() { |
105 // TODO(yoshiki): Implement this. | 170 if (!this.castMedia_) |
171 return; | |
172 | |
173 this.castMedia_.pause(null, | |
174 function () {}, | |
175 this.onCastCommandError_.wrap(this)); | |
106 }, | 176 }, |
107 | 177 |
108 /** | 178 /** |
109 * Loads the video. | 179 * Loads the video. |
110 */ | 180 */ |
111 load: function() { | 181 load: function(opt_callback) { |
112 // TODO(yoshiki): Implement this. | 182 var request = new chrome.cast.media.LoadRequest(this.mediaInfo_); |
183 this.castSession_.loadMedia(request, | |
184 function(media) { | |
185 this.onMediaDiscovered_(media); | |
186 if (opt_callback) | |
187 opt_callback(); | |
188 }.bind(this), | |
189 this.onCastCommandError_.wrap(this)); | |
190 }, | |
191 | |
192 /** | |
193 * Unloads the video. | |
194 * @private | |
195 */ | |
196 unloadMedia_: function() { | |
197 this.castMedia_.removeUpdateListener(this.onCastMediaUpdatedBound_); | |
198 this.castMedia_ = null; | |
199 clearInterval(this.updateTimerId_); | |
200 }, | |
201 | |
202 /** | |
203 * This method is called periodically to update media information while the | |
204 * media is loaded. | |
205 * @private | |
206 */ | |
207 onPeriodicalUpdateTimer_: function() { | |
208 if (!this.castMedia_) | |
209 return; | |
210 | |
211 if (this.castMedia_.playerState == chrome.cast.media.PlayerState.PLAYING) | |
212 this.onCastMediaUpdated_(true); | |
213 }, | |
214 | |
215 /** | |
216 * This method should be called when a media file is loaded. | |
217 * @param {chrome.cast.Media} media Media object which was discovered. | |
218 * @private | |
219 */ | |
220 onMediaDiscovered_: function(media) { | |
221 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.
| |
222 this.onCastMediaUpdated_(true); | |
223 media.addUpdateListener(this.onCastMediaUpdatedBound_); | |
224 this.updateTimerId_ = setInterval(this.onPeriodicalUpdateTimer_.bind(this), | |
225 MEDIA_UPDATE_INTERVAL); | |
226 }, | |
227 | |
228 /** | |
229 * This method should be called when a media command to cast is failed. | |
230 * @private | |
231 */ | |
232 onCastCommandError_: function() { | |
233 this.unloadMedia_(); | |
234 this.dispatchEvent(new Event('error')); | |
235 }, | |
236 | |
237 /** | |
238 * This is called when any media data is updated and by the periodical timer | |
239 * is fired. | |
240 * | |
241 * @param {boolean} alive Media availability. False if it's unavailable. | |
242 * @private | |
243 */ | |
244 onCastMediaUpdated_: function(alive) { | |
245 if (!this.castMedia_) | |
246 return; | |
247 | |
248 var media = this.castMedia_; | |
249 if (this.currentMediaPlayerState_ != media.playerState) { | |
250 var oldPlayState = false; | |
251 var oldState = this.currentMediaPlayerState_; | |
252 if (oldState == chrome.cast.media.PlayerState.BUFFERING || | |
253 oldState == chrome.cast.media.PlayerState.PLAYING) { | |
254 oldPlayState = true; | |
255 } | |
256 var newPlayState = false; | |
257 var newState = media.playerState; | |
258 if (newState == chrome.cast.media.PlayerState.BUFFERING || | |
259 newState == chrome.cast.media.PlayerState.PLAYING) { | |
260 newPlayState = true; | |
261 } | |
262 if (!oldPlayState && newPlayState) | |
263 this.dispatchEvent(new Event('play')); | |
264 if (oldPlayState && !newPlayState) | |
265 this.dispatchEvent(new Event('pause')); | |
266 | |
267 this.currentMediaPlayerState_ = newState; | |
268 } | |
269 if (this.currentMediaCurrentTime_ != media.getEstimatedTime()) { | |
270 this.dispatchEvent(new Event('timeupdate')); | |
271 this.currentMediaCurrentTime_ = media.getEstimatedTime(); | |
272 } | |
273 | |
274 if (this.currentMediaDuration_ != media.media.duration) { | |
275 this.dispatchEvent(new Event('durationchange')); | |
276 this.currentMediaDuration_ = media.media.duration; | |
277 } | |
278 | |
279 // Media is being unloaded. | |
280 if (!alive) { | |
281 this.unloadMedia_(); | |
282 return; | |
283 } | |
113 }, | 284 }, |
114 }; | 285 }; |
OLD | NEW |