Shaka Player Embedded
media_element.cc
Go to the documentation of this file.
1 // Copyright 2016 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
16 
17 #include <cmath>
18 
20 #include "src/js/dom/document.h"
21 #include "src/js/js_error.h"
23 #include "src/js/mse/time_ranges.h"
24 #include "src/media/media_utils.h"
25 #include "src/util/macros.h"
26 #include "src/util/utils.h"
27 
28 namespace shaka {
29 namespace js {
30 namespace mse {
31 
32 #define NOT_ATTACHED_ERROR \
33  JsError::DOMException(InvalidStateError, \
34  "The video has been detached from the MediaPlayer")
35 #define CHECK_ATTACHED() \
36  if (player_) \
37  ; \
38  else \
39  return NOT_ATTACHED_ERROR
40 
42  const std::string& name,
43  media::MediaPlayer* player)
44  : dom::Element(document, name, nullopt, nullopt),
45  autoplay(false),
46  loop(false),
47  default_muted(false),
48  audio_tracks(new AudioTrackList(player)),
49  video_tracks(new VideoTrackList(player)),
50  text_tracks(new TextTrackList(player)),
51  player_(player),
52  clock_(&util::Clock::Instance),
53  default_playback_rate_(1) {
54  AddListenerField(EventType::Encrypted, &on_encrypted);
55  AddListenerField(EventType::WaitingForKey, &on_waiting_for_key);
56  player_->AddClient(this);
57 }
58 
59 // \cond Doxygen_Skip
60 HTMLMediaElement::~HTMLMediaElement() {
61  if (player_)
62  Detach();
63 }
64 // \endcond Doxygen_Skip
65 
67  dom::Element::Trace(tracer);
68  tracer->Trace(&error);
69  tracer->Trace(&media_source_);
70  tracer->Trace(&audio_tracks);
71  tracer->Trace(&video_tracks);
72  tracer->Trace(&text_tracks);
73 }
74 
75 void HTMLMediaElement::RemoveAttribute(const std::string& attr) {
76  Element::RemoveAttribute(attr);
77  if (attr == "src") {
78  SetSource("");
79  }
80 }
81 
83  player_->RemoveClient(this);
84  player_ = nullptr;
85 
86  audio_tracks->Detach();
87  video_tracks->Detach();
88  text_tracks->Detach();
89 }
90 
91 
93  if (!player_)
95  if (!media_keys && !media_source_)
96  return Promise::Resolved();
97 
98  eme::Implementation* cdm = media_keys ? media_keys->GetCdm() : nullptr;
99  const std::string key_system = media_keys ? media_keys->key_system : "";
100  if (!player_->SetEmeImplementation(key_system, cdm)) {
101  return Promise::Rejected(
102  JsError::TypeError("Error changing MediaKeys on the MediaPlayer"));
103  }
104 
105  this->media_keys = media_keys;
106  return Promise::Resolved();
107 }
108 
110  // Normally, this should unload any old content and start the load process
111  // over. But Shaka Player calls this as part of startup, which would cause a
112  // longer delay before starting. We start loading when assigning 'src=', so
113  // we don't need to do anything here.
114 }
115 
117  auto info =
119  if (!player_)
120  return CanPlayTypeEnum::EMPTY;
121 
122  auto support = player_->DecodingInfo(info);
123  if (!support.supported)
124  return CanPlayTypeEnum::EMPTY;
125  else if (!support.smooth)
126  return CanPlayTypeEnum::MAYBE;
128 }
129 
131  if (!player_)
133 
134  auto ret = player_->ReadyState();
137  return ret;
138 }
139 
141  return new TimeRanges(player_ ? player_->GetBuffered()
142  : std::vector<media::BufferedRange>{});
143 }
144 
146  const double duration = Duration();
147  media::BufferedRanges ranges;
148  if (std::isfinite(duration))
149  ranges.emplace_back(0, duration);
150  return new TimeRanges(ranges);
151 }
152 
153 std::string HTMLMediaElement::Source() const {
154  return media_source_ ? media_source_->url : src_;
155 }
156 
158  CHECK_ATTACHED();
159 
160  // Unload old content.
161  if (media_source_) {
162  player_->Detach();
163  media_source_->CloseMediaSource();
164  media_source_.reset();
165  } else if (!src_.empty()) {
166  player_->Detach();
167  src_ = "";
168  }
169 
170  error = nullptr;
171  SetPlaybackRate(default_playback_rate_);
173 
174  if (src.empty())
175  return {};
176 
177  // Load new content.
178  media_source_ = MediaSource::FindMediaSource(src);
179  if (media_source_) {
180  if (!player_->AttachMse()) {
182  "Error attaching to MediaPlayer");
183  }
184  media_source_->OpenMediaSource(this, player_);
185 
186  if (autoplay)
187  player_->Play();
188  } else {
189  if (!player_->AttachSource(src)) {
191  "Given src= URL is unsupported");
192  }
193  src_ = src;
194  }
195  return {};
196 }
197 
199  return player_ ? player_->CurrentTime() : 0;
200 }
201 
203  CHECK_ATTACHED();
204  player_->SetCurrentTime(time);
205  return {};
206 }
207 
209  return player_ ? player_->Duration() : 0;
210 }
211 
213  return player_ ? player_->PlaybackRate() : 0;
214 }
215 
217  CHECK_ATTACHED();
218  if (!std::isfinite(rate)) {
219  return JsError::TypeError("The value provided is non-finite");
220  }
221 
222  player_->SetPlaybackRate(rate);
223  return {};
224 }
225 
227  return default_playback_rate_;
228 }
229 
231  if (!std::isfinite(rate)) {
232  return JsError::TypeError("The value provided is non-finite");
233  }
234 
235  default_playback_rate_ = rate;
236  return {};
237 }
238 
240  return player_ && player_->Muted();
241 }
242 
244  CHECK_ATTACHED();
245  player_->SetMuted(muted);
246  return {};
247 }
248 
249 double HTMLMediaElement::Volume() const {
250  return player_ ? player_->Volume() : 0;
251 }
252 
254  CHECK_ATTACHED();
255  if (volume < 0 || volume > 1) {
256  return JsError::DOMException(
259  "The volume provided (%f) is outside the range [0, 1].", volume));
260  }
261 
262  player_->SetVolume(volume);
263  return {};
264 }
265 
267  if (!player_)
268  return false;
269 
270  auto state = player_->PlaybackState();
271  return state == media::VideoPlaybackState::Initializing ||
274 }
275 
277  return player_ &&
279 }
280 
282  return player_ &&
284 }
285 
287  CHECK_ATTACHED();
288  player_->Play();
289  return {};
290 }
291 
293  CHECK_ATTACHED();
294  player_->Pause();
295  return {};
296 }
297 
300  optional<std::string> language) {
301  CHECK_ATTACHED();
302 
303  auto track =
304  player_->AddTextTrack(kind, label.value_or(""), language.value_or(""));
305  if (!track) {
306  return JsError::DOMException(UnknownError, "Error creating TextTrack");
307  }
308 
309  // The TextTrackList should already have gotten an event callback for the new
310  // track, so the JS object should be in the list.
311  RefPtr<TextTrack> ret = text_tracks->GetTrack(track);
312  DCHECK(ret);
313  return ret;
314 }
315 
316 void HTMLMediaElement::OnReadyStateChanged(media::VideoReadyState old_state,
317  media::VideoReadyState new_state) {
318  if (old_state < media::VideoReadyState::HaveMetadata &&
320  ScheduleEvent<events::Event>(EventType::LoadedMetaData);
321  }
322  if (old_state < media::VideoReadyState::HaveCurrentData &&
324  ScheduleEvent<events::Event>(EventType::LoadedData);
325  }
326  if (old_state < media::VideoReadyState::HaveFutureData &&
328  ScheduleEvent<events::Event>(EventType::CanPlay);
329  }
330  if (old_state < media::VideoReadyState::HaveEnoughData &&
332  ScheduleEvent<events::Event>(EventType::CanPlayThrough);
333  }
334 
335  if (old_state >= media::VideoReadyState::HaveFutureData &&
338  ScheduleEvent<events::Event>(EventType::Waiting);
339  }
340 
341  ScheduleEvent<events::Event>(EventType::ReadyStateChange);
342 }
343 
344 void HTMLMediaElement::OnPlaybackStateChanged(
346  switch (new_state) {
348  ScheduleEvent<events::Event>(EventType::Emptied);
349  break;
351  ScheduleEvent<events::Event>(EventType::Pause);
352  break;
354  ScheduleEvent<events::Event>(EventType::Waiting);
355  break;
357  ScheduleEvent<events::Event>(EventType::Playing);
358  break;
360  ScheduleEvent<events::Event>(EventType::Ended);
361  break;
362 
365  break;
367  // We also get an OnSeeking callback, so raise event there.
368  break;
370  // This happens multiple times, raise the event in the OnWaitingForKey.
371  break;
372  }
373  if (old_state == media::VideoPlaybackState::Seeking)
374  ScheduleEvent<events::Event>(EventType::Seeked);
375 }
376 
377 void HTMLMediaElement::OnPlaybackRateChanged(double old_rate, double new_rate) {
378  ScheduleEvent<events::Event>(EventType::RateChange);
379 }
380 
381 void HTMLMediaElement::OnError(const std::string& error) {
382  if (!this->error) {
383  if (error.empty())
384  this->error = new MediaError(MEDIA_ERR_DECODE, "Unknown media error");
385  else
386  this->error = new MediaError(MEDIA_ERR_DECODE, error);
387  }
388  ScheduleEvent<events::Event>(EventType::Error);
389 }
390 
391 void HTMLMediaElement::OnPlay() {
392  ScheduleEvent<events::Event>(EventType::Play);
393 }
394 
395 void HTMLMediaElement::OnSeeking() {
396  ScheduleEvent<events::Event>(EventType::Seeking);
397 }
398 
399 void HTMLMediaElement::OnWaitingForKey() {
400  ScheduleEvent<events::Event>(EventType::WaitingForKey);
401 }
402 
403 
405  AddConstant("HAVE_NOTHING", media::VideoReadyState::HaveNothing);
406  AddConstant("HAVE_METADATA", media::VideoReadyState::HaveMetadata);
407  AddConstant("HAVE_CURRENT_DATA", media::VideoReadyState::HaveCurrentData);
408  AddConstant("HAVE_FUTURE_DATA", media::VideoReadyState::HaveFutureData);
409  AddConstant("HAVE_ENOUGH_DATA", media::VideoReadyState::HaveEnoughData);
410 
411  AddListenerField(EventType::Encrypted, &HTMLMediaElement::on_encrypted);
412  AddListenerField(EventType::WaitingForKey,
414 
415  AddReadWriteProperty("autoplay", &HTMLMediaElement::autoplay);
416  AddReadWriteProperty("loop", &HTMLMediaElement::loop);
417  AddReadWriteProperty("defaultMuted", &HTMLMediaElement::default_muted);
418  AddReadOnlyProperty("mediaKeys", &HTMLMediaElement::media_keys);
419  AddReadOnlyProperty("error", &HTMLMediaElement::error);
420  AddReadOnlyProperty("audioTracks", &HTMLMediaElement::audio_tracks);
421  AddReadOnlyProperty("videoTracks", &HTMLMediaElement::video_tracks);
422  AddReadOnlyProperty("textTracks", &HTMLMediaElement::text_tracks);
423 
424  AddGenericProperty("readyState", &HTMLMediaElement::GetReadyState);
425  AddGenericProperty("paused", &HTMLMediaElement::Paused);
426  AddGenericProperty("seeking", &HTMLMediaElement::Seeking);
427  AddGenericProperty("ended", &HTMLMediaElement::Ended);
428  AddGenericProperty("buffered", &HTMLMediaElement::Buffered);
429  AddGenericProperty("seekable", &HTMLMediaElement::Seekable);
430  AddGenericProperty("src", &HTMLMediaElement::Source,
432  AddGenericProperty("currentSrc", &HTMLMediaElement::Source);
433  AddGenericProperty("currentTime", &HTMLMediaElement::CurrentTime,
435  AddGenericProperty("duration", &HTMLMediaElement::Duration);
436  AddGenericProperty("playbackRate", &HTMLMediaElement::PlaybackRate,
438  AddGenericProperty("defaultPlaybackRate",
441  AddGenericProperty("volume", &HTMLMediaElement::Volume,
443  AddGenericProperty("muted", &HTMLMediaElement::Muted,
445 
446  AddMemberFunction("load", &HTMLMediaElement::Load);
447  AddMemberFunction("play", &HTMLMediaElement::Play);
448  AddMemberFunction("pause", &HTMLMediaElement::Pause);
449  AddMemberFunction("setMediaKeys", &HTMLMediaElement::SetMediaKeys);
450  AddMemberFunction("addTextTrack", &HTMLMediaElement::AddTextTrack);
451  AddMemberFunction("canPlayType", &HTMLMediaElement::CanPlayType);
452 
453  NotImplemented("crossOrigin");
454  NotImplemented("networkState");
455  NotImplemented("preload");
456  NotImplemented("getStartDate");
457  NotImplemented("playable");
458  NotImplemented("mediaGroup");
459  NotImplemented("controller");
460  NotImplemented("controls");
461 }
462 
463 } // namespace mse
464 } // namespace js
465 } // namespace shaka
Member< VideoTrackList > video_tracks
Definition: media_element.h:80
#define NOT_ATTACHED_ERROR
virtual VideoPlaybackState PlaybackState() const =0
void RemoveAttribute(const std::string &attr) override
virtual double Volume() const =0
std::string StringPrintf(const char *format,...)
Definition: utils.cc:49
CanPlayTypeEnum CanPlayType(const std::string &type)
virtual void AddClient(Client *client) const =0
const char * name
Promise SetMediaKeys(RefPtr< eme::MediaKeys > media_keys)
virtual void RemoveClient(Client *client) const =0
const nullopt_t nullopt
Definition: optional.cc:22
Member< AudioTrackList > audio_tracks
Definition: media_element.h:79
ExceptionOr< void > SetPlaybackRate(double rate)
Member< TextTrackList > text_tracks
Definition: media_element.h:81
virtual bool AttachMse()=0
virtual void SetPlaybackRate(double rate)=0
void Trace(memory::HeapTracer *tracer) const override
Definition: element.cc:38
media::MediaPlayer * player_
virtual double PlaybackRate() const =0
ExceptionCode type
virtual void SetMuted(bool muted)=0
ExceptionOr< void > SetSource(const std::string &src)
void Trace(const Traceable *ptr)
Definition: heap_tracer.cc:43
MediaDecodingConfiguration ConvertMimeToDecodingConfiguration(const std::string &mime_type, MediaDecodingType type)
Definition: media_utils.cc:173
void Trace(memory::HeapTracer *tracer) const override
virtual void SetVolume(double volume)=0
Member< MediaError > error
Definition: media_element.h:78
virtual void Pause()=0
virtual bool SetEmeImplementation(const std::string &key_system, eme::Implementation *implementation)=0
virtual bool Muted() const =0
std::vector< BufferedRange > BufferedRanges
Definition: types.h:26
ExceptionOr< void > SetCurrentTime(double time)
ExceptionOr< RefPtr< TextTrack > > AddTextTrack(media::TextTrackKind kind, optional< std::string > label, optional< std::string > language)
static Promise Rejected(const js::JsError &error)
Definition: promise.h:77
virtual double CurrentTime() const =0
T value_or(U &&default_value) const &
Definition: optional.h:165
virtual void Play()=0
RefPtr< TimeRanges > Buffered() const
ExceptionOr< void > SetMuted(bool muted)
virtual bool AttachSource(const std::string &src)=0
virtual void Detach()=0
static JsError TypeError(const std::string &message)
Definition: js_error.cc:81
virtual double Duration() const =0
static RefPtr< MediaSource > FindMediaSource(const std::string &url)
ExceptionOr< void > SetVolume(double volume)
static JsError DOMException(ExceptionCode code)
Definition: js_error.cc:115
media::VideoReadyState GetReadyState() const
virtual VideoReadyState ReadyState() const =0
static Promise Resolved()
Definition: promise.h:64
virtual MediaCapabilitiesInfo DecodingInfo(const MediaDecodingConfiguration &config) const =0
virtual std::shared_ptr< TextTrack > AddTextTrack(TextTrackKind kind, const std::string &label, const std::string &language)=0
virtual std::vector< BufferedRange > GetBuffered() const =0
HTMLMediaElement(RefPtr< dom::Document > document, const std::string &name, media::MediaPlayer *player)
void AddListenerField(EventType type, Listener *on_field)
Definition: event_target.h:138
ExceptionOr< void > SetDefaultPlaybackRate(double rate)
Member< eme::MediaKeys > media_keys
Definition: media_element.h:67
virtual void SetCurrentTime(double time)=0
#define CHECK_ATTACHED()
RefPtr< TimeRanges > Seekable() const