Shaka Player Embedded
av_media_player.mm
Go to the documentation of this file.
1 // Copyright 2019 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 #import <AVFoundation/AVFoundation.h>
18 #import <AVKit/AVKit.h>
19 #include <glog/logging.h>
20 
21 #include <vector>
22 
23 #include "src/debug/mutex.h"
26 #include "src/util/macros.h"
27 #include "src/util/utils.h"
28 
29 #define LISTEN_PLAYER_KEY_PATHS \
30  { @"error", @"rate" }
31 #define LISTEN_ITEM_KEY_PATHS \
32  { @"status", @"playbackLikelyToKeepUp", @"playbackBufferEmpty" }
33 
34 @interface LayerWrapper : CALayer
35 @end
36 
37 @interface ValueObserver : NSObject
38 - (id)initWithImpl:(shaka::media::ios::AvMediaPlayer::Impl *)impl;
39 @end
40 
42  public:
43  Impl(ClientList *clients)
44  : mutex_("AvMediaPlayer"),
45  layer_([[LayerWrapper alloc] init]),
46  clients_(clients),
47  load_id_(0),
48  old_playback_state_(VideoPlaybackState::Detached),
49  old_ready_state_(VideoReadyState::NotAttached),
50  requested_play_(false),
51  loaded_(false),
52  player_(nil),
53  player_layer_(nil),
54  observer_([[ValueObserver alloc] initWithImpl:this]) {}
55 
56  ~Impl() {
57  Detach();
58  }
59 
61 
67  AVPlayer *GetPlayer() __attribute__((ns_returns_retained)) {
68  util::shared_lock<SharedMutex> lock(mutex_);
69  return player_;
70  }
71 
72  const void *GetIosView() {
73  return CFBridgingRetain(layer_);
74  }
75 
76  const void *GetAvPlayerAsPointer() {
77  util::shared_lock<SharedMutex> lock(mutex_);
78  return CFBridgingRetain(player_);
79  }
80 
82  clients_->AddClient(client);
83  }
84 
86  clients_->RemoveClient(client);
87  }
88 
89 
91  util::shared_lock<SharedMutex> lock(mutex_);
92  if (!player_layer_)
93  return false;
94 
95  switch (mode) {
96  case VideoFillMode::Stretch:
97  player_layer_.videoGravity = AVLayerVideoGravityResize;
98  break;
99  case VideoFillMode::Zoom:
100  player_layer_.videoGravity = AVLayerVideoGravityResizeAspectFill;
101  break;
102  case VideoFillMode::MaintainRatio:
103  player_layer_.videoGravity = AVLayerVideoGravityResizeAspect;
104  break;
105  default:
106  return false;
107  }
108  return true;
109  }
110 
111  void Play() {
112  AVPlayer *player = GetPlayer();
113  if (player) {
114  [player play];
115 
116  std::unique_lock<SharedMutex> lock(mutex_);
117  requested_play_ = true;
118  }
119  }
120 
121  void Pause() {
122  AVPlayer *player = GetPlayer();
123  if (player) {
124  [player pause];
125 
126  std::unique_lock<SharedMutex> lock(mutex_);
127  requested_play_ = false;
128  }
129  }
130 
131  bool Attach(const std::string &src) {
132  std::unique_lock<SharedMutex> lock(mutex_);
133  DCHECK(!player_) << "Already attached";
134 
135  auto *str = [NSString stringWithUTF8String:src.c_str()];
136  if (!str)
137  return false;
138  auto *url = [NSURL URLWithString:str];
139  if (!url)
140  return false;
141  player_ = [AVPlayer playerWithURL:url];
142  if (!player_)
143  return false;
144 
145  const auto options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
146  for (auto *name : LISTEN_PLAYER_KEY_PATHS)
147  [player_ addObserver:observer_ forKeyPath:name options:options context:nil];
148  for (auto *name : LISTEN_ITEM_KEY_PATHS)
149  [player_.currentItem addObserver:observer_ forKeyPath:name options:options context:nil];
150 
151  // Note that this must be done on the main thread for the layer to be visible. Since this is
152  // asynchronous, store the current load ID so we know if another attach/detach happened. We
153  // cannot wait for this to complete since that would block the JS main thread and could
154  // deadlock.
155  int load_id = ++load_id_;
156  dispatch_async(dispatch_get_main_queue(), ^{
157  std::unique_lock<SharedMutex> lock(mutex_);
158  if (load_id != load_id_)
159  return;
160 
161  player_layer_ = [AVPlayerLayer playerLayerWithPlayer:player_];
162  player_layer_.frame = layer_.frame;
163  [layer_ addSublayer:player_layer_];
164 
165  clients_->OnAttachSource();
166  });
167 
168  return true;
169  }
170 
171  void Detach() {
172  std::unique_lock<SharedMutex> lock(mutex_);
173  load_id_++;
174  if (player_) {
175  for (auto *name : LISTEN_PLAYER_KEY_PATHS)
176  [player_ removeObserver:observer_ forKeyPath:name];
177  for (auto *name : LISTEN_ITEM_KEY_PATHS)
178  [player_.currentItem removeObserver:observer_ forKeyPath:name];
179 
180  [player_ pause];
181  player_ = nil;
182  }
183  audio_tracks_.clear();
184  video_tracks_.clear();
185  text_tracks_.clear();
186  old_playback_state_ = VideoPlaybackState::Detached;
187  old_ready_state_ = VideoReadyState::NotAttached;
188  requested_play_ = false;
189  loaded_ = false;
190  player_layer_ = nil;
191 
192  for (CALayer *layer in [layer_.sublayers copy])
193  [layer removeFromSuperlayer];
194 
195  clients_->OnDetach();
196  }
197 
199  std::unique_lock<SharedMutex> lock(mutex_);
200  const auto new_state = VideoPlaybackState(player_);
201  if (old_playback_state_ != new_state) {
202  const auto old_state = old_playback_state_;
203  old_playback_state_ = new_state;
204  clients_->OnPlaybackStateChanged(old_state, new_state);
205  if (new_state == VideoPlaybackState::Playing && requested_play_)
206  clients_->OnPlay();
207 
208  requested_play_ = false;
209  }
210  return new_state;
211  }
212 
214  std::unique_lock<SharedMutex> lock(mutex_);
215  CheckLoaded();
216  const auto new_state = VideoReadyState(player_, loaded_);
217  if (old_ready_state_ != new_state) {
218  const auto old_state = old_ready_state_;
219  old_ready_state_ = new_state;
220  clients_->OnReadyStateChanged(old_state, new_state);
221  }
222  return new_state;
223  }
224 
225  std::vector<std::shared_ptr<MediaTrack>> AudioTracks() {
226  std::unique_lock<SharedMutex> lock(mutex_);
227  CheckLoaded();
228  return audio_tracks_;
229  }
230 
231  std::vector<std::shared_ptr<MediaTrack>> VideoTracks() {
232  std::unique_lock<SharedMutex> lock(mutex_);
233  CheckLoaded();
234  return video_tracks_;
235  }
236 
237  std::vector<std::shared_ptr<TextTrack>> TextTracks() {
238  std::unique_lock<SharedMutex> lock(mutex_);
239  CheckLoaded();
240  return text_tracks_;
241  }
242 
243  void OnError(const std::string &message) {
244  clients_->OnError(message);
245  }
246 
247  void OnPlaybackRateChanged(double old_rate, double new_rate) {
248  clients_->OnPlaybackRateChanged(old_rate, new_rate);
249  }
250 
251  private:
252  static VideoPlaybackState VideoPlaybackState(AVPlayer *player) {
253  if (!player)
254  return VideoPlaybackState::Detached;
255 
256  if (player.error)
257  return VideoPlaybackState::Errored;
258 
259  const double duration = CMTimeGetSeconds(player.currentItem.asset.duration);
260  const double time = CMTimeGetSeconds([player currentTime]);
261  if (!isnan(duration) && duration > 0 && time + 0.1 >= duration)
262  return VideoPlaybackState::Ended;
263 
264  if (player.rate == 0)
265  return VideoPlaybackState::Paused;
266  if (player.currentItem.playbackBufferEmpty)
267  return VideoPlaybackState::Buffering;
268 
269  return VideoPlaybackState::Playing;
270  }
271 
272  static VideoReadyState VideoReadyState(AVPlayer *player, bool loaded) {
273  if (!player)
274  return VideoReadyState::NotAttached;
275 
276  if (player.currentItem.status != AVPlayerItemStatusReadyToPlay || !loaded) {
277  return VideoReadyState::HaveNothing;
278  }
279  if (player.currentItem.playbackBufferEmpty) {
280  // TODO: This isn't correct. This will be false if we don't have enough data to move
281  // forward. But we may still be HaveCurrentData. There isn't an easy way to detect this
282  // case, so for now we just use HaveMetaData.
283  return VideoReadyState::HaveMetadata;
284  }
285 
286  if (!player.currentItem.playbackLikelyToKeepUp)
287  return VideoReadyState::HaveFutureData;
288  return VideoReadyState::HaveEnoughData;
289  }
290 
291  void CheckLoaded() {
292  if (loaded_ || !player_)
293  return;
294  if (player_.currentItem.tracks.count == 0)
295  return;
296 
297  // Can't change video tracks directly. Return an empty list for audio-only content and a
298  // dummy value for audio+video content.
299  for (AVPlayerItemTrack *track in player_.currentItem.tracks) {
300  if ([track.assetTrack.mediaType isEqualToString:AVMediaTypeVideo]) {
301  std::shared_ptr<MediaTrack> track(new MediaTrack(MediaTrackKind::Unknown, "", "", ""));
302  video_tracks_.emplace_back(track);
303  clients_->OnAddVideoTrack(track);
304  break;
305  }
306  }
307 
308  auto *item = player_.currentItem;
309  auto *group =
310  [item.asset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
311  if (group) {
312  for (AVMediaSelectionOption *option in [group options]) {
313  std::shared_ptr<MediaTrack> track(new AvMediaTrack(item, group, option));
314  audio_tracks_.emplace_back(track);
315  clients_->OnAddAudioTrack(track);
316  }
317  }
318 
319  group = [item.asset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
320  if (group) {
321  for (AVMediaSelectionOption *option in [group options]) {
322  std::shared_ptr<TextTrack> track(new AvTextTrack(item, group, option));
323  text_tracks_.emplace_back(track);
324  clients_->OnAddTextTrack(track);
325  }
326  }
327  loaded_ = true;
328  }
329 
330  SharedMutex mutex_;
331  LayerWrapper *const layer_;
332  ClientList *const clients_;
333 
334  std::vector<std::shared_ptr<MediaTrack>> audio_tracks_;
335  std::vector<std::shared_ptr<MediaTrack>> video_tracks_;
336  std::vector<std::shared_ptr<TextTrack>> text_tracks_;
337 
338  int load_id_;
339  enum VideoPlaybackState old_playback_state_;
340  enum VideoReadyState old_ready_state_;
341  bool requested_play_;
342  bool loaded_;
343 
344  AVPlayer *player_;
345  AVPlayerLayer *player_layer_;
346  ValueObserver *observer_;
347 };
348 
349 @implementation LayerWrapper
350 
351 + (Class)layerClass {
352  return [LayerWrapper class];
353 }
354 
355 - (void)layoutSublayers {
356  [super layoutSublayers];
357 
358  if (self.superlayer)
359  self.frame = self.superlayer.bounds;
360  for (CALayer *layer in self.sublayers)
361  layer.frame = self.bounds;
362 }
363 
364 @end
365 
366 @implementation ValueObserver {
368 }
369 
370 - (id)initWithImpl:(shaka::media::ios::AvMediaPlayer::Impl *)impl {
371  if (self) {
372  impl_ = impl;
373  }
374  return self;
375 }
376 
377 - (void)observeValueForKeyPath:(NSString *)keyPath
378  ofObject:(id)object
379  change:(NSDictionary<NSKeyValueChangeKey, id> *)change
380  context:(void *)context {
381  id old = [change objectForKey:NSKeyValueChangeOldKey];
382  id new_ = [change objectForKey:NSKeyValueChangeNewKey];
383  if ([keyPath isEqual:@"error"]) {
384  if (!old && new_) {
385  auto *error = static_cast<NSError *>(new_);
386  std::string str = [[error localizedDescription] UTF8String];
387  impl_->OnError(str);
388  }
389  } else if ([keyPath isEqual:@"rate"]) {
390  auto *old_num = static_cast<NSNumber *>(old);
391  auto *new_num = static_cast<NSNumber *>(new_);
392  impl_->OnPlaybackRateChanged([old_num doubleValue], [new_num doubleValue]);
393  }
394 
395  impl_->UpdateAndGetVideoPlaybackState();
396  impl_->UpdateAndGetVideoReadyState();
397 }
398 
399 @end
400 
401 
402 namespace shaka {
403 namespace media {
404 namespace ios {
405 
406 AvMediaPlayer::AvMediaPlayer(ClientList *clients) : impl_(new Impl(clients)) {}
408 
410  return impl_->GetIosView();
411 }
413  return impl_->GetAvPlayerAsPointer();
414 }
415 
418  if (config.type == MediaDecodingType::File &&
419  (!config.audio.content_type.empty() || !config.video.content_type.empty())) {
420  ret.supported = true;
421  if (!config.audio.content_type.empty()) {
422  auto *str = [NSString stringWithUTF8String:config.audio.content_type.c_str()];
423  ret.supported &= [AVURLAsset isPlayableExtendedMIMEType:str];
424  }
425  if (!config.video.content_type.empty()) {
426  auto *str = [NSString stringWithUTF8String:config.video.content_type.c_str()];
427  ret.supported &= [AVURLAsset isPlayableExtendedMIMEType:str];
428  }
429  }
430  return ret;
431 }
433  return {}; // Unsupported.
434 }
435 void AvMediaPlayer::AddClient(Client *client) const {
436  impl_->AddClient(client);
437 }
438 void AvMediaPlayer::RemoveClient(Client *client) const {
439  impl_->RemoveClient(client);
440 }
441 
442 std::vector<BufferedRange> AvMediaPlayer::GetBuffered() const {
443  AVPlayer *player = impl_->GetPlayer();
444 
445  std::vector<BufferedRange> ret;
446  if (player) {
447  auto *ranges = player.currentItem.loadedTimeRanges;
448  ret.reserve([ranges count]);
449  for (size_t i = 0; i < [ranges count]; i++) {
450  auto range = [[ranges objectAtIndex:i] CMTimeRangeValue];
451  auto start = CMTimeGetSeconds(range.start);
452  ret.emplace_back(start, start + CMTimeGetSeconds(range.duration));
453  }
454  }
455  return ret;
456 }
458  return impl_->UpdateAndGetVideoReadyState();
459 }
461  return impl_->UpdateAndGetVideoPlaybackState();
462 }
463 std::vector<std::shared_ptr<MediaTrack>> AvMediaPlayer::AudioTracks() {
464  return impl_->AudioTracks();
465 }
466 std::vector<std::shared_ptr<const MediaTrack>> AvMediaPlayer::AudioTracks() const {
467  auto ret = impl_->AudioTracks();
468  return {ret.begin(), ret.end()};
469 }
470 std::vector<std::shared_ptr<MediaTrack>> AvMediaPlayer::VideoTracks() {
471  return impl_->VideoTracks();
472 }
473 std::vector<std::shared_ptr<const MediaTrack>> AvMediaPlayer::VideoTracks() const {
474  auto ret = impl_->VideoTracks();
475  return {ret.begin(), ret.end()};
476 }
477 std::vector<std::shared_ptr<TextTrack>> AvMediaPlayer::TextTracks() {
478  return impl_->TextTracks();
479 }
480 std::vector<std::shared_ptr<const TextTrack>> AvMediaPlayer::TextTracks() const {
481  auto ret = impl_->TextTracks();
482  return {ret.begin(), ret.end()};
483 }
484 std::shared_ptr<TextTrack> AvMediaPlayer::AddTextTrack(TextTrackKind kind, const std::string &label,
485  const std::string &language) {
486  LOG(ERROR) << "Cannot add text tracks to src= content";
487  return nullptr;
488 }
489 
491  return impl_->SetVideoFillMode(mode);
492 }
493 uint32_t AvMediaPlayer::Width() const {
494  AVPlayer *player = impl_->GetPlayer();
495  if (player)
496  return static_cast<uint32_t>(player.currentItem.presentationSize.width);
497  else
498  return 0;
499 }
500 uint32_t AvMediaPlayer::Height() const {
501  AVPlayer *player = impl_->GetPlayer();
502  if (player)
503  return static_cast<uint32_t>(player.currentItem.presentationSize.height);
504  else
505  return 0;
506 }
507 double AvMediaPlayer::Volume() const {
508  AVPlayer *player = impl_->GetPlayer();
509  if (player)
510  return player.volume;
511  else
512  return 0;
513 }
514 void AvMediaPlayer::SetVolume(double volume) {
515  AVPlayer *player = impl_->GetPlayer();
516  if (player)
517  player.volume = static_cast<float>(volume);
518 }
519 bool AvMediaPlayer::Muted() const {
520  AVPlayer *player = impl_->GetPlayer();
521  if (player)
522  return player.muted;
523  else
524  return false;
525 }
526 void AvMediaPlayer::SetMuted(bool muted) {
527  AVPlayer *player = impl_->GetPlayer();
528  if (player)
529  player.muted = muted;
530 }
531 
533  impl_->Play();
534 }
536  impl_->Pause();
537 }
539  AVPlayer *player = impl_->GetPlayer();
540  if (player)
541  return CMTimeGetSeconds([player currentTime]);
542  else
543  return 0;
544 }
545 void AvMediaPlayer::SetCurrentTime(double time) {
546  AVPlayer *player = impl_->GetPlayer();
547  if (player)
548  [player seekToTime:CMTimeMakeWithSeconds(time, 1000)];
549 }
550 double AvMediaPlayer::Duration() const {
551  AVPlayer *player = impl_->GetPlayer();
552  if (player)
553  return CMTimeGetSeconds(player.currentItem.asset.duration);
554  else
555  return 0;
556 }
557 void AvMediaPlayer::SetDuration(double duration) {
558  LOG(FATAL) << "Cannot set duration of src= assets";
559 }
561  AVPlayer *player = impl_->GetPlayer();
562  if (player)
563  return player.rate;
564  else
565  return 0;
566 }
568  AVPlayer *player = impl_->GetPlayer();
569  if (player)
570  player.rate = static_cast<float>(rate);
571 }
572 
573 bool AvMediaPlayer::AttachSource(const std::string &src) {
574  return impl_->Attach(src);
575 }
577  return false;
578 }
579 bool AvMediaPlayer::AddMseBuffer(const std::string &mime, bool is_video,
580  const ElementaryStream *stream) {
581  return false;
582 }
583 void AvMediaPlayer::LoadedMetaData(double duration) {}
585 bool AvMediaPlayer::SetEmeImplementation(const std::string &key_system,
586  eme::Implementation *implementation) {
587  return false;
588 }
590  impl_->Detach();
591 }
592 
593 } // namespace ios
594 } // namespace media
595 } // namespace shaka
bool AddMseBuffer(const std::string &mime, bool is_video, const ElementaryStream *stream) override
void AddClient(Client *client) const override
double Volume() const override
void SetCurrentTime(double time) override
void SetVolume(double volume) override
uint32_t Height() const override
VideoFillMode
Definition: utils.h:41
const char * name
std::vector< std::shared_ptr< MediaTrack > > AudioTracks() override
#define SHAKA_NON_COPYABLE_OR_MOVABLE_TYPE(Type)
Definition: macros.h:51
void SetPlaybackRate(double rate) override
std::vector< std::shared_ptr< MediaTrack > > VideoTracks()
void OnPlaybackRateChanged(double old_rate, double new_rate)
double Duration() const override
VideoPlaybackState UpdateAndGetVideoPlaybackState()
bool SetEmeImplementation(const std::string &key_system, eme::Implementation *implementation) override
VideoPlaybackState PlaybackState() const override
VideoReadyState ReadyState() const override
void OnError(const std::string &message)
#define LISTEN_PLAYER_KEY_PATHS
struct VideoPlaybackQuality VideoPlaybackQuality() const override
bool Attach(const std::string &src)
void AddClient(MediaPlayer::Client *client)
std::vector< std::shared_ptr< TextTrack > > TextTracks()
void RemoveClient(Client *client) const override
std::shared_ptr< TextTrack > AddTextTrack(TextTrackKind kind, const std::string &label, const std::string &language) override
AVPlayer * GetPlayer() __attribute__((ns_returns_retained))
double PlaybackRate() const override
std::vector< BufferedRange > GetBuffered() const override
void SetMuted(bool muted) override
std::vector< std::shared_ptr< MediaTrack > > AudioTracks()
void LoadedMetaData(double duration) override
std::vector< std::shared_ptr< TextTrack > > TextTracks() override
void RemoveClient(MediaPlayer::Client *client)
bool SetVideoFillMode(VideoFillMode mode) override
const char * message
MediaCapabilitiesInfo DecodingInfo(const MediaDecodingConfiguration &config) const override
std::vector< std::shared_ptr< MediaTrack > > VideoTracks() override
#define LISTEN_ITEM_KEY_PATHS
bool AttachSource(const std::string &src) override
bool SetVideoFillMode(VideoFillMode mode)
double CurrentTime() const override
void SetDuration(double duration) override
uint32_t Width() const override