1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/base/android/media_codec_player.h"
7 #include "base/barrier_closure.h"
9 #include "base/lazy_instance.h"
10 #include "base/logging.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "base/threading/thread.h"
13 #include "media/base/android/media_codec_audio_decoder.h"
14 #include "media/base/android/media_codec_video_decoder.h"
15 #include "media/base/android/media_player_manager.h"
16 #include "media/base/buffers.h"
18 #define RUN_ON_MEDIA_THREAD(METHOD, ...) \
20 if (!GetMediaTaskRunner()->BelongsToCurrentThread()) { \
21 DCHECK(ui_task_runner_->BelongsToCurrentThread()); \
22 GetMediaTaskRunner()->PostTask( \
23 FROM_HERE, base::Bind(&MediaCodecPlayer::METHOD, media_weak_this_, \
31 class MediaThread
: public base::Thread
{
33 MediaThread() : base::Thread("BrowserMediaThread") {
38 // Create media thread
39 base::LazyInstance
<MediaThread
>::Leaky
40 g_media_thread
= LAZY_INSTANCE_INITIALIZER
;
43 scoped_refptr
<base::SingleThreadTaskRunner
> GetMediaTaskRunner() {
44 return g_media_thread
.Pointer()->task_runner();
47 // MediaCodecPlayer implementation.
49 MediaCodecPlayer::MediaCodecPlayer(
51 base::WeakPtr
<MediaPlayerManager
> manager
,
52 const RequestMediaResourcesCB
& request_media_resources_cb
,
53 scoped_ptr
<DemuxerAndroid
> demuxer
,
54 const GURL
& frame_url
)
55 : MediaPlayerAndroid(player_id
,
57 request_media_resources_cb
,
59 ui_task_runner_(base::ThreadTaskRunnerHandle::Get()),
60 demuxer_(demuxer
.Pass()),
62 interpolator_(&default_tick_clock_
),
63 pending_start_(false),
64 media_weak_factory_(this) {
65 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
67 DVLOG(1) << "MediaCodecPlayer::MediaCodecPlayer: player_id:" << player_id
;
69 request_resources_cb_
= base::Bind(request_media_resources_cb_
, player_id
);
72 base::Bind(&MediaPlayerManager::OnPlaybackComplete
, manager
, player_id
);
73 attach_listener_cb_
= base::Bind(&MediaPlayerAndroid::AttachListener
,
74 WeakPtrForUIThread(), nullptr);
76 base::Bind(&MediaPlayerAndroid::DetachListener
, WeakPtrForUIThread());
77 metadata_changed_cb_
= base::Bind(&MediaPlayerAndroid::OnMediaMetadataChanged
,
78 WeakPtrForUIThread());
80 base::Bind(&MediaPlayerAndroid::OnTimeUpdate
, WeakPtrForUIThread());
82 media_weak_this_
= media_weak_factory_
.GetWeakPtr();
84 // Finish initializaton on Media thread
85 GetMediaTaskRunner()->PostTask(
86 FROM_HERE
, base::Bind(&MediaCodecPlayer::Initialize
, media_weak_this_
));
89 MediaCodecPlayer::~MediaCodecPlayer()
91 DVLOG(1) << "MediaCodecPlayer::~MediaCodecPlayer";
92 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
95 void MediaCodecPlayer::Initialize() {
96 DVLOG(1) << __FUNCTION__
;
97 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
99 interpolator_
.SetUpperBound(base::TimeDelta());
103 // This call might in turn call MediaCodecPlayer::OnDemuxerConfigsAvailable()
104 // which propagates configs into decoders. Therefore CreateDecoders() should
106 demuxer_
->Initialize(this);
109 // The implementation of MediaPlayerAndroid interface.
111 void MediaCodecPlayer::DeleteOnCorrectThread() {
112 DVLOG(1) << __FUNCTION__
;
113 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
117 // The base class part that deals with MediaPlayerListener
118 // has to be destroyed on UI thread.
119 DestroyListenerOnUIThread();
121 // Post deletion onto Media thread
122 GetMediaTaskRunner()->DeleteSoon(FROM_HERE
, this);
125 void MediaCodecPlayer::SetVideoSurface(gfx::ScopedJavaSurface surface
) {
126 RUN_ON_MEDIA_THREAD(SetVideoSurface
, base::Passed(&surface
));
128 DVLOG(1) << __FUNCTION__
<< (surface
.IsEmpty() ? " empty" : " non-empty");
130 // I assume that if video decoder already has the surface,
131 // there will be two calls:
132 // (1) SetVideoSurface(0)
133 // (2) SetVideoSurface(new_surface)
134 video_decoder_
->SetPendingSurface(surface
.Pass());
136 if (video_decoder_
->HasPendingSurface() &&
137 state_
== STATE_WAITING_FOR_SURFACE
) {
138 SetState(STATE_PLAYING
);
139 StartPlaybackDecoders();
143 void MediaCodecPlayer::Start() {
144 RUN_ON_MEDIA_THREAD(Start
);
146 DVLOG(1) << __FUNCTION__
;
150 if (HasAudio() || HasVideo()) {
151 SetState(STATE_PREFETCHING
);
152 StartPrefetchDecoders();
154 SetState(STATE_WAITING_FOR_CONFIG
);
158 SetPendingStart(true);
166 void MediaCodecPlayer::Pause(bool is_media_related_action
) {
167 RUN_ON_MEDIA_THREAD(Pause
, is_media_related_action
);
169 DVLOG(1) << __FUNCTION__
;
172 case STATE_PREFETCHING
:
173 SetState(STATE_PAUSED
);
176 case STATE_WAITING_FOR_SURFACE
:
177 SetState(STATE_PAUSED
);
181 SetState(STATE_STOPPING
);
182 RequestToStopDecoders();
190 void MediaCodecPlayer::SeekTo(base::TimeDelta timestamp
) {
191 RUN_ON_MEDIA_THREAD(SeekTo
, timestamp
);
193 DVLOG(1) << __FUNCTION__
<< " " << timestamp
;
197 void MediaCodecPlayer::Release() {
198 RUN_ON_MEDIA_THREAD(Release
);
200 DVLOG(1) << __FUNCTION__
;
202 SetState(STATE_PAUSED
);
203 ReleaseDecoderResources();
206 void MediaCodecPlayer::SetVolume(double volume
) {
207 RUN_ON_MEDIA_THREAD(SetVolume
, volume
);
209 DVLOG(1) << __FUNCTION__
<< " " << volume
;
210 audio_decoder_
->SetVolume(volume
);
213 int MediaCodecPlayer::GetVideoWidth() {
214 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
215 return metadata_cache_
.video_size
.width();
218 int MediaCodecPlayer::GetVideoHeight() {
219 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
220 return metadata_cache_
.video_size
.height();
223 base::TimeDelta
MediaCodecPlayer::GetCurrentTime() {
224 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
225 return current_time_cache_
;
228 base::TimeDelta
MediaCodecPlayer::GetDuration() {
229 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
230 return metadata_cache_
.duration
;
233 bool MediaCodecPlayer::IsPlaying() {
234 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
235 return state_
== STATE_PLAYING
;
238 bool MediaCodecPlayer::CanPause() {
239 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
244 bool MediaCodecPlayer::CanSeekForward() {
245 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
250 bool MediaCodecPlayer::CanSeekBackward() {
251 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
256 bool MediaCodecPlayer::IsPlayerReady() {
257 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
258 // This method is called to check whether it's safe to release the player when
259 // the OS needs more resources. This class can be released at any time.
263 void MediaCodecPlayer::SetCdm(BrowserCdm
* cdm
) {
264 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
268 // Callbacks from Demuxer.
270 void MediaCodecPlayer::OnDemuxerConfigsAvailable(
271 const DemuxerConfigs
& configs
) {
272 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
274 DVLOG(1) << __FUNCTION__
;
276 duration_
= configs
.duration
;
278 SetDemuxerConfigs(configs
);
280 // Update cache and notify manager on UI thread
281 gfx::Size video_size
= HasVideo() ? configs
.video_size
: gfx::Size();
282 ui_task_runner_
->PostTask(
283 FROM_HERE
, base::Bind(metadata_changed_cb_
, duration_
, video_size
));
286 void MediaCodecPlayer::OnDemuxerDataAvailable(const DemuxerData
& data
) {
287 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
289 DCHECK_LT(0u, data
.access_units
.size());
290 CHECK_GE(1u, data
.demuxer_configs
.size());
292 DVLOG(2) << "Player::" << __FUNCTION__
;
294 if (data
.type
== DemuxerStream::AUDIO
)
295 audio_decoder_
->OnDemuxerDataAvailable(data
);
297 if (data
.type
== DemuxerStream::VIDEO
)
298 video_decoder_
->OnDemuxerDataAvailable(data
);
301 void MediaCodecPlayer::OnDemuxerSeekDone(
302 base::TimeDelta actual_browser_seek_time
) {
303 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
305 DVLOG(1) << __FUNCTION__
<< " actual_time:" << actual_browser_seek_time
;
310 void MediaCodecPlayer::OnDemuxerDurationChanged(
311 base::TimeDelta duration
) {
312 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
313 DVLOG(1) << __FUNCTION__
<< " duration:" << duration
;
315 duration_
= duration
;
318 // Events from Player, called on UI thread
320 void MediaCodecPlayer::OnMediaMetadataChanged(base::TimeDelta duration
,
321 const gfx::Size
& video_size
) {
322 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
324 if (duration
!= kNoTimestamp())
325 metadata_cache_
.duration
= duration
;
327 if (!video_size
.IsEmpty())
328 metadata_cache_
.video_size
= video_size
;
330 manager()->OnMediaMetadataChanged(player_id(), metadata_cache_
.duration
,
331 metadata_cache_
.video_size
.width(),
332 metadata_cache_
.video_size
.height(), true);
335 void MediaCodecPlayer::OnTimeUpdate(base::TimeDelta current_timestamp
,
336 base::TimeTicks current_time_ticks
) {
337 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
339 current_time_cache_
= current_timestamp
;
340 manager()->OnTimeUpdate(player_id(), current_timestamp
, current_time_ticks
);
343 // Events from Decoders, called on Media thread
345 void MediaCodecPlayer::RequestDemuxerData(DemuxerStream::Type stream_type
) {
346 DVLOG(2) << __FUNCTION__
<< " streamType:" << stream_type
;
348 // Use this method instead of directly binding with
349 // DemuxerAndroid::RequestDemuxerData() to avoid the race condition on
351 // 1. DeleteSoon is posted from UI to Media thread.
352 // 2. RequestDemuxerData callback is posted from Decoder to Media thread.
353 // 3. DeleteSoon arrives, we delete the player and detach from
354 // BrowserDemuxerAndroid.
355 // 4. RequestDemuxerData is processed by the media thread queue. Since the
356 // weak_ptr was invalidated in (3), this is a no-op. If we used
357 // DemuxerAndroid::RequestDemuxerData() it would arrive and will try to
358 // call the client, but the client (i.e. this player) would not exist.
359 demuxer_
->RequestDemuxerData(stream_type
);
362 void MediaCodecPlayer::OnPrefetchDone() {
363 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
364 DVLOG(1) << __FUNCTION__
;
366 if (state_
!= STATE_PREFETCHING
)
369 if (!HasAudio() && !HasVideo()) {
370 // No configuration at all after prefetching.
371 // This is an error, initial configuration is expected
372 // before the first data chunk.
373 GetMediaTaskRunner()->PostTask(FROM_HERE
, error_cb_
);
377 if (HasVideo() && !HasPendingSurface()) {
378 SetState(STATE_WAITING_FOR_SURFACE
);
382 SetState(STATE_PLAYING
);
383 StartPlaybackDecoders();
386 void MediaCodecPlayer::OnStopDone() {
387 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
388 DVLOG(1) << __FUNCTION__
;
390 if (!(audio_decoder_
->IsStopped() && video_decoder_
->IsStopped()))
391 return; // Wait until other stream is stopped
393 // At this point decoder threads should not be running
394 if (interpolator_
.interpolating())
395 interpolator_
.StopInterpolating();
399 if (HasPendingStart()) {
400 SetPendingStart(false);
401 SetState(STATE_PREFETCHING
);
402 StartPrefetchDecoders();
404 SetState(STATE_PAUSED
);
408 // Unexpected stop means completion
409 SetState(STATE_PAUSED
);
412 DVLOG(0) << __FUNCTION__
<< " illegal state: " << AsString(state_
);
417 // DetachListener to UI thread
418 ui_task_runner_
->PostTask(FROM_HERE
, detach_listener_cb_
);
420 if (AudioFinished() && VideoFinished())
421 ui_task_runner_
->PostTask(FROM_HERE
, completion_cb_
);
424 void MediaCodecPlayer::OnError() {
425 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
426 DVLOG(1) << __FUNCTION__
;
428 // STATE_ERROR blocks all events
429 SetState(STATE_ERROR
);
431 ReleaseDecoderResources();
434 void MediaCodecPlayer::OnStarvation(DemuxerStream::Type type
) {
435 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
436 DVLOG(1) << __FUNCTION__
<< " stream type:" << type
;
438 if (state_
!= STATE_PLAYING
)
441 SetState(STATE_STOPPING
);
442 RequestToStopDecoders();
443 SetPendingStart(true);
446 void MediaCodecPlayer::OnTimeIntervalUpdate(DemuxerStream::Type type
,
447 base::TimeDelta now_playing
,
448 base::TimeDelta last_buffered
) {
449 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
451 interpolator_
.SetBounds(now_playing
, last_buffered
);
454 ui_task_runner_
->PostTask(FROM_HERE
,
455 base::Bind(time_update_cb_
, GetInterpolatedTime(),
456 base::TimeTicks::Now()));
459 void MediaCodecPlayer::OnVideoCodecCreated() {
460 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
462 // This callback requests resources by releasing other players.
463 ui_task_runner_
->PostTask(FROM_HERE
, request_resources_cb_
);
466 void MediaCodecPlayer::OnVideoResolutionChanged(const gfx::Size
& size
) {
467 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
469 DVLOG(1) << __FUNCTION__
<< " " << size
.width() << "x" << size
.height();
471 // Update cache and notify manager on UI thread
472 ui_task_runner_
->PostTask(
473 FROM_HERE
, base::Bind(metadata_changed_cb_
, kNoTimestamp(), size
));
476 // State machine operations, called on Media thread
478 void MediaCodecPlayer::SetState(PlayerState new_state
) {
479 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
481 DVLOG(1) << "SetState:" << AsString(state_
) << " -> " << AsString(new_state
);
485 void MediaCodecPlayer::SetPendingSurface(gfx::ScopedJavaSurface surface
) {
486 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
487 DVLOG(1) << __FUNCTION__
;
489 video_decoder_
->SetPendingSurface(surface
.Pass());
492 bool MediaCodecPlayer::HasPendingSurface() {
493 return video_decoder_
->HasPendingSurface();
496 void MediaCodecPlayer::SetPendingStart(bool need_to_start
) {
497 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
498 DVLOG(1) << __FUNCTION__
<< ": " << need_to_start
;
499 pending_start_
= need_to_start
;
502 bool MediaCodecPlayer::HasPendingStart() {
503 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
504 return pending_start_
;
507 bool MediaCodecPlayer::HasAudio() {
508 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
509 return audio_decoder_
->HasStream();
512 bool MediaCodecPlayer::HasVideo() {
513 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
514 return video_decoder_
->HasStream();
517 void MediaCodecPlayer::SetDemuxerConfigs(const DemuxerConfigs
& configs
) {
518 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
519 DVLOG(1) << __FUNCTION__
<< " " << configs
;
521 DCHECK(audio_decoder_
);
522 DCHECK(video_decoder_
);
524 // At least one valid codec must be present.
525 DCHECK(configs
.audio_codec
!= kUnknownAudioCodec
||
526 configs
.video_codec
!= kUnknownVideoCodec
);
528 if (configs
.audio_codec
!= kUnknownAudioCodec
)
529 audio_decoder_
->SetDemuxerConfigs(configs
);
531 if (configs
.video_codec
!= kUnknownVideoCodec
)
532 video_decoder_
->SetDemuxerConfigs(configs
);
534 if (state_
== STATE_WAITING_FOR_CONFIG
) {
535 SetState(STATE_PREFETCHING
);
536 StartPrefetchDecoders();
540 void MediaCodecPlayer::StartPrefetchDecoders() {
541 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
542 DVLOG(1) << __FUNCTION__
;
544 bool do_audio
= false;
545 bool do_video
= false;
547 if (!AudioFinished()) {
551 if (!VideoFinished()) {
556 DCHECK_LT(0, count
); // at least one decoder should be active
558 base::Closure prefetch_cb
= base::BarrierClosure(
559 count
, base::Bind(&MediaCodecPlayer::OnPrefetchDone
, media_weak_this_
));
562 audio_decoder_
->Prefetch(prefetch_cb
);
565 video_decoder_
->Prefetch(prefetch_cb
);
568 void MediaCodecPlayer::StartPlaybackDecoders() {
569 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
570 DVLOG(1) << __FUNCTION__
;
572 // Configure all streams before the start since
573 // we may discover that browser seek is required.
575 bool do_audio
= !AudioFinished();
576 bool do_video
= !VideoFinished();
578 // If there is nothing to play, the state machine should determine
579 // this at the prefetch state and never call this method.
580 DCHECK(do_audio
|| do_video
);
583 MediaCodecDecoder::ConfigStatus status
= audio_decoder_
->Configure();
584 if (status
!= MediaCodecDecoder::CONFIG_OK
) {
585 GetMediaTaskRunner()->PostTask(FROM_HERE
, error_cb_
);
591 MediaCodecDecoder::ConfigStatus status
= video_decoder_
->Configure();
592 if (status
!= MediaCodecDecoder::CONFIG_OK
) {
593 GetMediaTaskRunner()->PostTask(FROM_HERE
, error_cb_
);
598 // At this point decoder threads should not be running.
599 if (!interpolator_
.interpolating())
600 interpolator_
.StartInterpolating();
602 base::TimeDelta current_time
= GetInterpolatedTime();
605 if (!audio_decoder_
->Start(current_time
)) {
606 GetMediaTaskRunner()->PostTask(FROM_HERE
, error_cb_
);
610 // Attach listener on UI thread
611 ui_task_runner_
->PostTask(FROM_HERE
, attach_listener_cb_
);
615 if (!video_decoder_
->Start(current_time
)) {
616 GetMediaTaskRunner()->PostTask(FROM_HERE
, error_cb_
);
622 void MediaCodecPlayer::StopDecoders() {
623 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
624 DVLOG(1) << __FUNCTION__
;
626 audio_decoder_
->SyncStop();
627 video_decoder_
->SyncStop();
630 void MediaCodecPlayer::RequestToStopDecoders() {
631 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
632 DVLOG(1) << __FUNCTION__
;
634 bool do_audio
= false;
635 bool do_video
= false;
637 if (audio_decoder_
->IsPrefetchingOrPlaying())
639 if (video_decoder_
->IsPrefetchingOrPlaying())
642 if (!do_audio
&& !do_video
) {
643 GetMediaTaskRunner()->PostTask(
644 FROM_HERE
, base::Bind(&MediaCodecPlayer::OnStopDone
, media_weak_this_
));
649 audio_decoder_
->RequestToStop();
651 video_decoder_
->RequestToStop();
654 void MediaCodecPlayer::ReleaseDecoderResources() {
655 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
656 DVLOG(1) << __FUNCTION__
;
659 audio_decoder_
->ReleaseDecoderResources();
662 video_decoder_
->ReleaseDecoderResources();
664 // At this point decoder threads should not be running
665 if (interpolator_
.interpolating())
666 interpolator_
.StopInterpolating();
669 void MediaCodecPlayer::CreateDecoders() {
670 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
671 DVLOG(1) << __FUNCTION__
;
673 error_cb_
= base::Bind(&MediaCodecPlayer::OnError
, media_weak_this_
);
675 audio_decoder_
.reset(new MediaCodecAudioDecoder(
676 GetMediaTaskRunner(), base::Bind(&MediaCodecPlayer::RequestDemuxerData
,
677 media_weak_this_
, DemuxerStream::AUDIO
),
678 base::Bind(&MediaCodecPlayer::OnStarvation
, media_weak_this_
,
679 DemuxerStream::AUDIO
),
680 base::Bind(&MediaCodecPlayer::OnStopDone
, media_weak_this_
), error_cb_
,
681 base::Bind(&MediaCodecPlayer::OnTimeIntervalUpdate
, media_weak_this_
,
682 DemuxerStream::AUDIO
)));
684 video_decoder_
.reset(new MediaCodecVideoDecoder(
685 GetMediaTaskRunner(), base::Bind(&MediaCodecPlayer::RequestDemuxerData
,
686 media_weak_this_
, DemuxerStream::VIDEO
),
687 base::Bind(&MediaCodecPlayer::OnStarvation
, media_weak_this_
,
688 DemuxerStream::VIDEO
),
689 base::Bind(&MediaCodecPlayer::OnStopDone
, media_weak_this_
), error_cb_
,
690 MediaCodecDecoder::SetTimeCallback(), // null callback
691 base::Bind(&MediaCodecPlayer::OnVideoResolutionChanged
, media_weak_this_
),
692 base::Bind(&MediaCodecPlayer::OnVideoCodecCreated
, media_weak_this_
)));
695 bool MediaCodecPlayer::AudioFinished() {
696 return audio_decoder_
->IsCompleted() || !audio_decoder_
->HasStream();
699 bool MediaCodecPlayer::VideoFinished() {
700 return video_decoder_
->IsCompleted() || !video_decoder_
->HasStream();
703 base::TimeDelta
MediaCodecPlayer::GetInterpolatedTime() {
704 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread());
706 base::TimeDelta interpolated_time
= interpolator_
.GetInterpolatedTime();
707 return std::min(interpolated_time
, duration_
);
711 #define RETURN_STRING(x) \
715 const char* MediaCodecPlayer::AsString(PlayerState state
) {
717 RETURN_STRING(STATE_PAUSED
);
718 RETURN_STRING(STATE_WAITING_FOR_CONFIG
);
719 RETURN_STRING(STATE_PREFETCHING
);
720 RETURN_STRING(STATE_PLAYING
);
721 RETURN_STRING(STATE_STOPPING
);
722 RETURN_STRING(STATE_WAITING_FOR_SURFACE
);
723 RETURN_STRING(STATE_ERROR
);
725 return nullptr; // crash early