Shaka Player Embedded
media_source.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 <glog/logging.h>
18 
19 #include <algorithm>
20 #include <cmath>
21 #include <functional>
22 #include <utility>
23 #include <vector>
24 
25 #include "shaka/media/demuxer.h"
26 #include "src/js/events/event.h"
32 #include "src/media/media_utils.h"
33 #include "src/util/macros.h"
34 
35 namespace shaka {
36 namespace js {
37 namespace mse {
38 
39 namespace {
40 
42 std::string RandomUrl() {
43  unsigned char bytes[16];
44  // Use pseudo-random since we don't need cryptographic security.
45  for (unsigned char& chr : bytes)
46  chr = static_cast<unsigned char>(rand()); // NOLINT
47 
48  // Since it's random, don't care about host byte order.
49 #define _2_BYTES_AT(b) (*reinterpret_cast<uint16_t*>(b))
50 #define _4_BYTES_AT(b) (*reinterpret_cast<uint32_t*>(b))
51 
52  return util::StringPrintf(
53  "blob:%08x-%04x-%04x-%04x-%08x%04x", _4_BYTES_AT(bytes),
54  _2_BYTES_AT(bytes + 4),
55  // Only output 3 hex chars, the first is the UUID version (4, Random).
56  (_2_BYTES_AT(bytes + 6) & 0xfff) | 0x4000,
57  // Drop the two high bits to set the variant (0b10xx).
58  (_2_BYTES_AT(bytes + 8) & 0x3fff) | 0x8000, _4_BYTES_AT(bytes + 10),
59  _2_BYTES_AT(bytes + 14));
60 }
61 
62 } // namespace
63 
64 using namespace std::placeholders; // NOLINT
65 
67 std::unordered_map<std::string, Member<MediaSource>>
68  MediaSource::media_sources_;
70 
72  : ready_state(MediaSourceReadyState::CLOSED),
73  url(RandomUrl()),
74  player_(nullptr),
75  got_loaded_metadata_(false) {
76  AddListenerField(EventType::SourceOpen, &on_source_open);
77  AddListenerField(EventType::SourceEnded, &on_source_ended);
78  AddListenerField(EventType::SourceClose, &on_source_close);
79 
80  DCHECK_EQ(0u, media_sources_.count(url));
81  media_sources_[url] = this;
82 }
83 
84 // \cond Doxygen_Skip
85 MediaSource::~MediaSource() {
86  DCHECK_EQ(1u, media_sources_.count(url));
87  media_sources_.erase(url);
88 }
89 // \endcond Doxygen_Skip
90 
91 // static
92 bool MediaSource::IsTypeSupported(const std::string& mime_type) {
94  if (!player)
96  if (!player) {
97  LOG(ERROR) << "Unable to find a MediaPlayer instance to query";
98  return false;
99  }
100 
103  auto support = player->DecodingInfo(info);
104  return support.supported;
105 }
106 
107 // static
109  if (media_sources_.count(url) == 0)
110  return nullptr;
111  return media_sources_[url];
112 }
113 
115  EventTarget::Trace(tracer);
116  tracer->Trace(&audio_buffer_);
117  tracer->Trace(&video_buffer_);
118  tracer->Trace(&video_);
119 }
120 
122  const std::string& type) {
124  return JsError::DOMException(
126  R"(Cannot call addSourceBuffer() unless MediaSource is "open".)");
127  }
128  DCHECK(player_);
129 
130  std::unordered_map<std::string, std::string> params;
131  if (!media::ParseMimeType(type, nullptr, nullptr, &params)) {
132  return JsError::DOMException(
134  "The given type ('" + type + "') is not a valid MIME type.");
135  }
136  const std::string codecs = params[media::kCodecMimeParam];
137 
139  if (!factory) {
141  "No Demuxer implementation provided");
142  }
143  if (!factory->IsTypeSupported(type) || codecs.empty() ||
144  codecs.find(',') != std::string::npos) {
145  return JsError::DOMException(
146  NotSupportedError, "The given type ('" + type + "') is unsupported.");
147  }
148 
149  const bool is_video = factory->IsCodecVideo(codecs);
150  RefPtr<SourceBuffer> ret = new SourceBuffer(type, this);
151  RefPtr<SourceBuffer> existing = is_video ? video_buffer_ : audio_buffer_;
152  if (!existing.empty()) {
154  "Invalid SourceBuffer configuration");
155  }
156 
157  if (!ret->Attach(type, player_, is_video)) {
158  return JsError::DOMException(UnknownError, "Error attaching SourceBuffer");
159  }
160  if (is_video)
161  video_buffer_ = ret;
162  else
163  audio_buffer_ = ret;
164  return ret;
165 }
166 
169  return JsError::DOMException(
171  R"(Cannot call endOfStream() unless MediaSource is "open".)");
172  }
173  if ((video_buffer_ && video_buffer_->updating) ||
174  (audio_buffer_ && audio_buffer_->updating)) {
175  return JsError::DOMException(
177  "Cannot call endOfStream() when a SourceBuffer is updating.");
178  }
179  if (error.has_value()) {
180  return JsError::DOMException(
182  "Calling endOfStream() with an argument is not supported.");
183  }
184 
186  ScheduleEvent<events::Event>(EventType::SourceEnded);
187 
188  player_->MseEndOfStream();
189  return {};
190 }
191 
192 double MediaSource::GetDuration() const {
193  return player_ ? player_->Duration() : NAN;
194 }
195 
197  if (std::isnan(duration))
198  return JsError::TypeError("Cannot set duration to NaN.");
200  return JsError::DOMException(
202  R"(Cannot change duration unless MediaSource is "open".)");
203  }
204  if ((video_buffer_ && video_buffer_->updating) ||
205  (audio_buffer_ && audio_buffer_->updating)) {
206  return JsError::DOMException(
208  "Cannot change duration when a SourceBuffer is updating.");
209  }
210 
211  DCHECK(player_);
212  player_->SetDuration(duration);
213  return {};
214 }
215 
217  media::MediaPlayer* player) {
219  << "MediaSource already attached to a <video> element.";
221  video_ = video;
222  player_ = player;
223  ScheduleEvent<events::Event>(EventType::SourceOpen);
224 }
225 
228  << "MediaSource not attached to a <video> element.";
229 
231  video_ = nullptr;
232  player_ = nullptr;
233 
234  if (video_buffer_) {
235  video_buffer_->Detach();
236  video_buffer_.reset();
237  }
238  if (audio_buffer_) {
239  audio_buffer_->Detach();
240  audio_buffer_.reset();
241  }
242 
243  ScheduleEvent<events::Event>(EventType::SourceClose);
244 }
245 
246 void MediaSource::OnLoadedMetaData(double duration) {
247  bool raise;
248  if (got_loaded_metadata_) {
249  // We only get this event once per buffer; so if this is called a second
250  // time, we must have two buffers.
251  raise = true;
252  } else {
253  // Raise if we only have one buffer.
254  raise = video_buffer_.empty() != audio_buffer_.empty();
255  }
256  if (raise && player_)
257  player_->LoadedMetaData(duration);
258  got_loaded_metadata_ = true;
259 }
260 
261 void MediaSource::OnEncrypted(eme::MediaKeyInitDataType type,
262  const uint8_t* data, size_t size) {
263  if (video_) {
264  video_->ScheduleEvent<events::MediaEncryptedEvent>(
265  EventType::Encrypted, type, ByteBuffer(data, size));
266  }
267 }
268 
269 
271  AddListenerField(EventType::SourceOpen, &MediaSource::on_source_open);
272  AddListenerField(EventType::SourceEnded, &MediaSource::on_source_ended);
273  AddListenerField(EventType::SourceClose, &MediaSource::on_source_close);
274 
275  AddReadOnlyProperty("readyState", &MediaSource::ready_state);
276 
277  AddGenericProperty("duration", &MediaSource::GetDuration,
279 
280  AddMemberFunction("addSourceBuffer", &MediaSource::AddSourceBuffer);
281  AddMemberFunction("endOfStream", &MediaSource::EndOfStream);
282 
283  AddStaticFunction("isTypeSupported", &MediaSource::IsTypeSupported);
284 
285  NotImplemented("activeSourceBuffers");
286  NotImplemented("clearLiveSeekableRange");
287  NotImplemented("removeSourceBuffer");
288  NotImplemented("setLiveSeekableRange");
289  NotImplemented("sourceBuffers");
290 }
291 
292 } // namespace mse
293 } // namespace js
294 } // namespace shaka
virtual BackingObjectFactoryBase * factory() const =0
bool ParseMimeType(const std::string &source, std::string *type, std::string *subtype, std::unordered_map< std::string, std::string > *params)
Definition: media_utils.cc:62
ExceptionOr< void > SetDuration(double duration)
#define END_ALLOW_COMPLEX_STATICS
Definition: macros.h:43
virtual void MseEndOfStream()=0
virtual void LoadedMetaData(double duration)=0
#define _4_BYTES_AT(b)
std::string StringPrintf(const char *format,...)
Definition: utils.cc:49
MediaSourceReadyState ready_state
Definition: media_source.h:79
static const DemuxerFactory * GetFactory()
Definition: demuxer.cc:53
static const MediaPlayer * GetMediaPlayerForSupportChecks()
ExceptionCode type
static media::MediaPlayer * AnyMediaPlayer()
void Trace(const Traceable *ptr)
Definition: heap_tracer.cc:43
bool empty() const
Definition: ref_ptr.h:120
MediaDecodingConfiguration ConvertMimeToDecodingConfiguration(const std::string &mime_type, MediaDecodingType type)
Definition: media_utils.cc:173
virtual void SetDuration(double duration)=0
void OpenMediaSource(RefPtr< HTMLMediaElement > video, media::MediaPlayer *player)
#define BEGIN_ALLOW_COMPLEX_STATICS
Definition: macros.h:42
constexpr const char * kCodecMimeParam
Definition: media_utils.h:33
ExceptionOr< void > EndOfStream(optional< std::string > error)
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)
void Trace(memory::HeapTracer *tracer) const override
static JsError DOMException(ExceptionCode code)
Definition: js_error.cc:115
ExceptionOr< RefPtr< SourceBuffer > > AddSourceBuffer(const std::string &type)
static bool IsTypeSupported(const std::string &mime_type)
Definition: media_source.cc:92
const std::string url
Definition: media_source.h:80
#define _2_BYTES_AT(b)
bool has_value() const
Definition: optional.h:143
void AddListenerField(EventType type, Listener *on_field)
Definition: event_target.h:138