Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qandroidmediaplayer.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include "wrappers/jni/androidmediaplayer_p.h"
6#include "common/qandroidvideooutput_p.h"
8#include "common/qandroidaudiooutput_p.h"
9#include "qaudiooutput.h"
10
11#include <QtMultimedia/private/qplatformvideosink_p.h>
12#include <qloggingcategory.h>
13
15
16using TrackType = QPlatformMediaPlayer::TrackType;
17
18Q_STATIC_LOGGING_CATEGORY(lcMediaPlayer, "qt.multimedia.mediaplayer.android");
19
21{
22public:
24 : mControl(mp)
27 {
28 ++mControl->mActiveStateChangeNotifiers;
29 }
30
32 {
33 if (--mControl->mActiveStateChangeNotifiers)
34 return;
35
36 if (mPreviousMediaStatus != mControl->mediaStatus())
37 Q_EMIT mControl->mediaStatusChanged(mControl->mediaStatus());
38
39 if (mPreviousState != mControl->state())
40 Q_EMIT mControl->stateChanged(mControl->state());
41 }
42
43private:
44 QAndroidMediaPlayer *mControl;
45 QMediaPlayer::PlaybackState mPreviousState;
46 QMediaPlayer::MediaStatus mPreviousMediaStatus;
47};
48
49QAndroidMediaPlayer::QAndroidMediaPlayer(QMediaPlayer *parent)
50 : QPlatformMediaPlayer(parent),
51 mMediaPlayer(new AndroidMediaPlayer),
53{
54 // Set seekable to True by default. It changes if MEDIA_INFO_NOT_SEEKABLE is received
55 seekableChanged(true);
56 connect(mMediaPlayer, &AndroidMediaPlayer::bufferingChanged, this,
57 &QAndroidMediaPlayer::onBufferingChanged);
58 connect(mMediaPlayer, &AndroidMediaPlayer::info, this, &QAndroidMediaPlayer::onInfo);
59 connect(mMediaPlayer, &AndroidMediaPlayer::error, this, &QAndroidMediaPlayer::onError);
60 connect(mMediaPlayer, &AndroidMediaPlayer::stateChanged, this,
61 &QAndroidMediaPlayer::onStateChanged);
62 connect(mMediaPlayer, &AndroidMediaPlayer::videoSizeChanged, this,
63 &QAndroidMediaPlayer::onVideoSizeChanged);
64 connect(mMediaPlayer, &AndroidMediaPlayer::progressChanged, this,
65 &QAndroidMediaPlayer::positionChanged);
66 connect(mMediaPlayer, &AndroidMediaPlayer::durationChanged, this,
67 &QAndroidMediaPlayer::durationChanged);
68 connect(mMediaPlayer, &AndroidMediaPlayer::tracksInfoChanged, this,
69 &QAndroidMediaPlayer::updateTrackInfo);
70}
71
73{
74 if (m_videoSink)
75 disconnect(m_videoSink->platformVideoSink(), nullptr, this, nullptr);
76
77 mMediaPlayer->disconnect();
78 mMediaPlayer->release();
79 delete mMediaPlayer;
80}
81
83{
84 if (mediaStatus() == QMediaPlayer::NoMedia)
85 return 0;
86
87 if ((mState & (AndroidMediaPlayer::Prepared
92 return 0;
93 }
94
95 return mMediaPlayer->getDuration();
96}
97
99{
100 if (mediaStatus() == QMediaPlayer::EndOfMedia)
101 return duration();
102
103 if ((mState & (AndroidMediaPlayer::Prepared
107 return mMediaPlayer->getCurrentPosition();
108 }
109
110 return (mPendingPosition == -1) ? 0 : mPendingPosition;
111}
112
113void QAndroidMediaPlayer::setPosition(qint64 position)
114{
115 if (!isSeekable())
116 return;
117
118 const int seekPosition = (position > INT_MAX) ? INT_MAX : position;
119
120 qint64 currentPosition = mMediaPlayer->getCurrentPosition();
121 if (seekPosition == currentPosition) {
122 // update position - will send a new frame of this position
123 // for consistency with other platforms
124 mMediaPlayer->seekTo(seekPosition);
125 return;
126 }
127 StateChangeNotifier notifier(this);
128
129 if (mediaStatus() == QMediaPlayer::EndOfMedia)
130 setMediaStatus(QMediaPlayer::LoadedMedia);
131
132 if ((mState & (AndroidMediaPlayer::Prepared
136 mPendingPosition = seekPosition;
137 } else {
138 mMediaPlayer->seekTo(seekPosition);
139
140 if (mPendingPosition != -1) {
141 mPendingPosition = -1;
142 }
143 }
144
145 Q_EMIT positionChanged(seekPosition);
146}
147
148void QAndroidMediaPlayer::setVolume(float volume)
149{
150 if ((mState & (AndroidMediaPlayer::Idle
157 mPendingVolume = volume;
158 return;
159 }
160
161 mMediaPlayer->setVolume(qRound(volume*100.));
162 mPendingVolume = -1;
163}
164
165void QAndroidMediaPlayer::setMuted(bool muted)
166{
167 if ((mState & (AndroidMediaPlayer::Idle
174 mPendingMute = muted;
175 return;
176 }
177
178 mMediaPlayer->setMuted(muted);
179 mPendingMute = -1;
180}
181
183{
184 return QAndroidMetaData::extractMetadata(mMediaContent);
185}
186
188{
189 return mBufferFilled ? 1. : mBufferPercent;
190}
191
193{
194 return mAudioAvailable;
195}
196
198{
199 return mVideoAvailable;
200}
201
203{
204 return mAvailablePlaybackRange;
205}
206
207void QAndroidMediaPlayer::updateAvailablePlaybackRanges()
208{
209 if (mBuffering) {
210 const qint64 pos = position();
211 const qint64 end = (duration() / 100) * mBufferPercent;
212 mAvailablePlaybackRange.addInterval(pos, end);
213 } else if (isSeekable()) {
214 mAvailablePlaybackRange = QMediaTimeRange(0, duration());
215 } else {
216 mAvailablePlaybackRange = QMediaTimeRange();
217 }
218
219// #### Q_EMIT availablePlaybackRangesChanged(mAvailablePlaybackRange);
220}
221
223{
224 return mCurrentPlaybackRate;
225}
226
228{
229 if (mState != AndroidMediaPlayer::Started) {
230 // If video isn't playing, changing speed rate may start it automatically
231 // It need to be postponed
232 if (mCurrentPlaybackRate != rate) {
233 mCurrentPlaybackRate = rate;
234 mHasPendingPlaybackRate = true;
235 Q_EMIT playbackRateChanged(rate);
236 }
237 return;
238 }
239
240 if (mMediaPlayer->setPlaybackRate(rate)) {
241 mCurrentPlaybackRate = rate;
242 Q_EMIT playbackRateChanged(rate);
243 }
244}
245
247{
248 return mMediaContent;
249}
250
252{
253 return mMediaStream;
254}
255
256void QAndroidMediaPlayer::setMedia(const QUrl &mediaContent,
257 QIODevice *stream)
258{
259 StateChangeNotifier notifier(this);
260
261 mReloadingMedia = (mMediaContent == mediaContent) && !mPendingSetMedia;
262
263 if (!mReloadingMedia) {
264 mMediaContent = mediaContent;
265 mMediaStream = stream;
266 }
267
268 if (mediaContent.isEmpty()) {
269 setMediaStatus(QMediaPlayer::NoMedia);
270 } else {
271 if (mVideoOutput && !mVideoOutput->isReady()) {
272 // if a video output is set but the video texture is not ready, delay loading the media
273 // since it can cause problems on some hardware
274 mPendingSetMedia = true;
275 return;
276 }
277
278 if (mVideoSize.isValid() && mVideoOutput)
279 mVideoOutput->setVideoSize(mVideoSize);
280
281 if (mVideoOutput &&
282 (mMediaPlayer->display() == 0 || mVideoOutput->shouldTextureBeUpdated()))
283 mMediaPlayer->setDisplay(mVideoOutput->surfaceTexture());
284 mMediaPlayer->setDataSource(QNetworkRequest(mediaContent));
285 mMediaPlayer->prepareAsync();
286
287 if (!mReloadingMedia)
288 setMediaStatus(QMediaPlayer::LoadingMedia);
289 }
290
291 resetBufferingProgress();
292
293 mReloadingMedia = false;
294}
295
296void QAndroidMediaPlayer::setVideoSink(QVideoSink *sink)
297{
298 if (m_videoSink == sink)
299 return;
300
301 if (m_videoSink)
302 disconnect(m_videoSink->platformVideoSink(), nullptr, this, nullptr);
303
304 m_videoSink = sink;
305
306 if (!m_videoSink) {
307 return;
308 }
309
310 if (mVideoOutput) {
311 delete mVideoOutput;
312 mVideoOutput = nullptr;
313 mMediaPlayer->setDisplay(nullptr);
314 }
315
316 mVideoOutput = new QAndroidTextureVideoOutput(sink, this);
317 connect(mVideoOutput, &QAndroidTextureVideoOutput::readyChanged, this,
318 &QAndroidMediaPlayer::onVideoOutputReady);
319 connect(mMediaPlayer, &AndroidMediaPlayer::timedTextChanged, mVideoOutput,
320 &QAndroidTextureVideoOutput::setSubtitle);
321
322 if (mVideoOutput->isReady())
323 mMediaPlayer->setDisplay(mVideoOutput->surfaceTexture());
324
325 if (auto *rhiSink = qobject_cast<QRhiVideoSink *>(m_videoSink->platformVideoSink()))
326 connect(rhiSink, &QRhiVideoSink::rhiChanged, this, [&]()
327 { mMediaPlayer->setDisplay(mVideoOutput->surfaceTexture()); });
328}
329
330void QAndroidMediaPlayer::setAudioOutput(QPlatformAudioOutput *output)
331{
332 if (m_audioOutput == output)
333 return;
334 if (m_audioOutput)
335 m_audioOutput->q->disconnect(this);
336 m_audioOutput = static_cast<QAndroidAudioOutput *>(output);
337 if (m_audioOutput) {
338 connect(m_audioOutput->q, &QAudioOutput::deviceChanged, this, &QAndroidMediaPlayer::updateAudioDevice);
339 connect(m_audioOutput->q, &QAudioOutput::volumeChanged, this, &QAndroidMediaPlayer::setVolume);
340 connect(m_audioOutput->q, &QAudioOutput::mutedChanged, this, &QAndroidMediaPlayer::setMuted);
342 }
343}
344
346{
347 if (m_audioOutput)
348 mMediaPlayer->setAudioOutput(m_audioOutput->device.id());
349}
350
352{
353 StateChangeNotifier notifier(this);
354
355 resetCurrentLoop();
356
357 // We need to prepare the mediaplayer again.
358 if ((mState & AndroidMediaPlayer::Stopped) && !mMediaContent.isEmpty()) {
359 setMedia(mMediaContent, mMediaStream);
360 }
361
362 if (!mMediaContent.isEmpty())
363 stateChanged(QMediaPlayer::PlayingState);
364
365 if ((mState & (AndroidMediaPlayer::Prepared
369 mPendingState = QMediaPlayer::PlayingState;
370 return;
371 }
372
373 if (mVideoOutput)
374 mVideoOutput->start();
375
377
378 if (mHasPendingPlaybackRate) {
379 mHasPendingPlaybackRate = false;
380 if (mMediaPlayer->setPlaybackRate(mCurrentPlaybackRate))
381 return;
382 mCurrentPlaybackRate = mMediaPlayer->playbackRate();
383 Q_EMIT playbackRateChanged(mCurrentPlaybackRate);
384 }
385
386 mMediaPlayer->play();
387}
388
390{
391 // cannot pause without media
392 if (mediaStatus() == QMediaPlayer::NoMedia)
393 return;
394
395 StateChangeNotifier notifier(this);
396
397 stateChanged(QMediaPlayer::PausedState);
398
399 if ((mState & (AndroidMediaPlayer::Started
404 mPendingState = QMediaPlayer::PausedState;
405 return;
406 }
407
408 const qint64 currentPosition = mMediaPlayer->getCurrentPosition();
409 setPosition(currentPosition);
410
411 mMediaPlayer->pause();
412}
413
415{
416 StateChangeNotifier notifier(this);
417
418 stateChanged(QMediaPlayer::StoppedState);
419
420 if ((mState & (AndroidMediaPlayer::Prepared
426 mPendingState = QMediaPlayer::StoppedState;
427 return;
428 }
429
430 if (mCurrentPlaybackRate != 1.)
431 // Playback rate need to by reapplied
432 mHasPendingPlaybackRate = true;
433
434 if (mVideoOutput)
435 mVideoOutput->stop();
436
437 mMediaPlayer->stop();
438}
439
440void QAndroidMediaPlayer::onInfo(qint32 what, qint32 extra)
441{
442 StateChangeNotifier notifier(this);
443
444 Q_UNUSED(extra);
445 switch (what) {
447 break;
449 // IGNORE
450 break;
452 break;
454 mPendingState = state();
455 stateChanged(QMediaPlayer::PausedState);
456 setMediaStatus(QMediaPlayer::StalledMedia);
457 break;
459 if (state() != QMediaPlayer::StoppedState)
460 flushPendingStates();
461 break;
463 break;
465 seekableChanged(false);
466 break;
468 Q_EMIT metaDataChanged();
469 break;
470 }
471}
472
473void QAndroidMediaPlayer::onError(qint32 what, qint32 extra)
474{
475 StateChangeNotifier notifier(this);
476
477 QString errorString;
478 QMediaPlayer::Error error = QMediaPlayer::ResourceError;
479
480 switch (what) {
482 errorString = QLatin1String("Error:");
483 break;
485 errorString = QLatin1String("Error: Server died");
486 error = QMediaPlayer::ResourceError;
487 break;
489 errorString = QLatin1String("Error: Invalid state");
490 error = QMediaPlayer::ResourceError;
491 break;
492 }
493
494 switch (extra) {
495 case AndroidMediaPlayer::MEDIA_ERROR_IO: // Network OR file error
496 errorString += QLatin1String(" (I/O operation failed)");
497 setInvalidMediaWithError(QMediaPlayer::NetworkError, errorString);
498 return;
500 errorString += QLatin1String(" (Malformed bitstream)");
501 setInvalidMediaWithError(QMediaPlayer::FormatError, errorString);
502 return;
504 errorString += QLatin1String(" (Unsupported media)");
505 setInvalidMediaWithError(QMediaPlayer::FormatError, errorString);
506 return;
508 errorString += QLatin1String(" (Timed out)");
509 break;
511 errorString += QLatin1String(" (Unable to start progressive playback')");
512 setInvalidMediaWithError(QMediaPlayer::FormatError, errorString);
513 return;
515 errorString += mMediaContent.scheme() == QLatin1String("rtsp")
516 ? QLatin1String(" (Unknown error/Insufficient resources or RTSP may not be supported)")
517 : QLatin1String(" (Unknown error/Insufficient resources)");
518 error = QMediaPlayer::ResourceError;
519 break;
520 }
521
522 Q_EMIT QPlatformMediaPlayer::error(error, errorString);
523}
524
525void QAndroidMediaPlayer::onBufferingChanged(qint32 percent)
526{
527 StateChangeNotifier notifier(this);
528
529 mBuffering = percent != 100;
530 mBufferPercent = percent;
531
532 updateAvailablePlaybackRanges();
533
534 if (state() != QMediaPlayer::StoppedState)
535 setMediaStatus(mBuffering ? QMediaPlayer::BufferingMedia : QMediaPlayer::BufferedMedia);
536
537 updateBufferStatus();
538}
539
540void QAndroidMediaPlayer::onVideoSizeChanged(qint32 width, qint32 height)
541{
542 QSize newSize(width, height);
543
544 if (width == 0 || height == 0 || newSize == mVideoSize)
545 return;
546
547 setVideoAvailable(true);
548 mVideoSize = newSize;
549
550 if (mVideoOutput)
551 mVideoOutput->setVideoSize(mVideoSize);
552}
553
554void QAndroidMediaPlayer::onStateChanged(qint32 state)
555{
556 // If reloading, don't report state changes unless the new state is Prepared or Error.
557 if ((mState & AndroidMediaPlayer::Stopped)
559 return;
560 }
561
562 StateChangeNotifier notifier(this);
563
564 mState = state;
565 switch (mState) {
567 break;
569 break;
570 case AndroidMediaPlayer::Preparing:
571 if (!mReloadingMedia)
572 setMediaStatus(QMediaPlayer::LoadingMedia);
573 break;
574 case AndroidMediaPlayer::Prepared:
575 setMediaStatus(QMediaPlayer::LoadedMedia);
576 if (mBuffering) {
577 setMediaStatus(mBufferPercent == 100 ? QMediaPlayer::BufferedMedia
578 : QMediaPlayer::BufferingMedia);
579 } else {
580 onBufferingChanged(100);
581 }
582 setPosition(0);
583 Q_EMIT metaDataChanged();
584 setAudioAvailable(true);
585 flushPendingStates();
586 break;
588 stateChanged(QMediaPlayer::PlayingState);
589 if (mBuffering) {
590 setMediaStatus(mBufferPercent == 100 ? QMediaPlayer::BufferedMedia
591 : QMediaPlayer::BufferingMedia);
592 } else {
593 setMediaStatus(QMediaPlayer::BufferedMedia);
594 }
595 Q_EMIT positionChanged(position());
596 break;
598 stateChanged(QMediaPlayer::PausedState);
599 if (mediaStatus() == QMediaPlayer::EndOfMedia) {
600 setPosition(0);
601 setMediaStatus(QMediaPlayer::BufferedMedia);
602 } else {
603 Q_EMIT positionChanged(position());
604 }
605 break;
607 stateChanged(QMediaPlayer::StoppedState);
608 setMediaStatus(QMediaPlayer::InvalidMedia);
609 mMediaPlayer->release();
610 Q_EMIT positionChanged(0);
611 break;
613 stateChanged(QMediaPlayer::StoppedState);
614 setMediaStatus(QMediaPlayer::LoadedMedia);
615 Q_EMIT positionChanged(0);
616 break;
618 if (doLoop()) {
619 setPosition(0);
620 mMediaPlayer->play();
621 break;
622 }
623 stateChanged(QMediaPlayer::StoppedState);
624 setMediaStatus(QMediaPlayer::EndOfMedia);
625 break;
627 // reset some properties (unless we reload the same media)
628 if (!mReloadingMedia) {
629 resetBufferingProgress();
630 mPendingPosition = -1;
631 mPendingSetMedia = false;
632 mPendingState = -1;
633
634 Q_EMIT durationChanged(0);
635 Q_EMIT positionChanged(0);
636
637 setAudioAvailable(false);
638 setVideoAvailable(false);
639 seekableChanged(true);
640 }
641 break;
642 default:
643 break;
644 }
645
647 mMediaPlayer->setDisplay(0);
648 if (mVideoOutput) {
649 mVideoOutput->stop();
650 }
651 }
652}
653
654int QAndroidMediaPlayer::trackCount(TrackType trackType)
655{
656 if (!mTracksMetadata.contains(trackType))
657 return -1;
658
659 auto tracks = mTracksMetadata.value(trackType);
660 return tracks.count();
661}
662
663QMediaMetaData QAndroidMediaPlayer::trackMetaData(TrackType trackType, int streamNumber)
664{
665 if (!mTracksMetadata.contains(trackType))
666 return QMediaMetaData();
667
668 auto tracks = mTracksMetadata.value(trackType);
669 if (tracks.count() < streamNumber)
670 return QMediaMetaData();
671
672 QAndroidMetaData trackInfo = tracks.at(streamNumber);
673 return static_cast<QMediaMetaData>(trackInfo);
674}
675
677{
678 switch (type) {
679 case AndroidMediaPlayer::TrackType::Video:
680 return TrackType::VideoStream;
681 case AndroidMediaPlayer::TrackType::Audio:
682 return TrackType::AudioStream;
683 case AndroidMediaPlayer::TrackType::TimedText:
684 return TrackType::SubtitleStream;
685 case AndroidMediaPlayer::TrackType::Subtitle:
686 return TrackType::SubtitleStream;
687 case AndroidMediaPlayer::TrackType::Unknown:
688 case AndroidMediaPlayer::TrackType::Metadata:
689 return std::nullopt;
690 }
691
692 return std::nullopt;
693}
694
695int QAndroidMediaPlayer::convertTrackNumber(int androidTrackNumber)
696{
697 int trackNumber = androidTrackNumber;
698
699 int videoTrackCount = trackCount(TrackType::VideoStream);
700 if (trackNumber <= videoTrackCount)
701 return trackNumber;
702
703 trackNumber = trackNumber - videoTrackCount;
704
705 int audioTrackCount = trackCount(TrackType::AudioStream);
706 if (trackNumber <= audioTrackCount)
707 return trackNumber;
708
709 trackNumber = trackNumber - audioTrackCount;
710
711 auto subtitleTracks = mTracksMetadata.value(TrackType::SubtitleStream);
712 int timedTextCount = 0;
713 int subtitleTextCount = 0;
714 for (const auto &track : subtitleTracks) {
715 if (track.androidTrackType() == 3) // 3 == TimedText
716 timedTextCount++;
717
718 if (track.androidTrackType() == 4) // 4 == Subtitle
719 subtitleTextCount++;
720 }
721
722 if (trackNumber <= timedTextCount)
723 return trackNumber;
724
725 trackNumber = trackNumber - timedTextCount;
726
727 if (trackNumber <= subtitleTextCount)
728 return trackNumber;
729
730 return -1;
731}
732
733int QAndroidMediaPlayer::activeTrack(TrackType trackType)
734{
735 int androidTrackNumber = -1;
736
737 switch (trackType) {
738 case TrackType::VideoStream: {
739 if (!mIsVideoTrackEnabled)
740 return -1;
741 androidTrackNumber = mMediaPlayer->activeTrack(AndroidMediaPlayer::TrackType::Video);
742 }
743 case TrackType::AudioStream: {
744 if (!mIsAudioTrackEnabled)
745 return -1;
746
747 androidTrackNumber = mMediaPlayer->activeTrack(AndroidMediaPlayer::TrackType::Audio);
748 }
749 case TrackType::SubtitleStream: {
750 int timedTextSelectedTrack =
752
753 if (timedTextSelectedTrack > -1) {
754 androidTrackNumber = timedTextSelectedTrack;
755 break;
756 }
757
758 int subtitleSelectedTrack =
760 if (subtitleSelectedTrack > -1) {
761 androidTrackNumber = subtitleSelectedTrack;
762 break;
763 }
764
765 return -1;
766 }
767 }
768
769 return convertTrackNumber(androidTrackNumber);
770}
771
772void QAndroidMediaPlayer::disableTrack(TrackType trackType)
773{
774 const auto track = activeTrack(trackType);
775
776 switch (trackType) {
777 case TrackType::VideoStream: {
778 if (track > -1) {
779 mMediaPlayer->setDisplay(nullptr);
780 mIsVideoTrackEnabled = false;
781 }
782 break;
783 }
784 case TrackType::AudioStream: {
785 if (track > -1) {
786 mMediaPlayer->setMuted(true);
787 mMediaPlayer->blockAudio();
788 mIsAudioTrackEnabled = false;
789 }
790 break;
791 }
792 case TrackType::SubtitleStream: {
793 // subtitles and timedtext tracks can be selected at the same time so deselect both
794 int subtitleSelectedTrack =
796 if (subtitleSelectedTrack > -1)
797 mMediaPlayer->deselectTrack(subtitleSelectedTrack);
798
799 int timedTextSelectedTrack =
801 if (timedTextSelectedTrack > -1)
802 mMediaPlayer->deselectTrack(timedTextSelectedTrack);
803
804 break;
805 }
806 }
807}
808
809void QAndroidMediaPlayer::setActiveTrack(TrackType trackType, int streamNumber)
810{
811
812 if (!mTracksMetadata.contains(trackType)) {
813 qCWarning(lcMediaPlayer)
814 << "Trying to set a active track which type has no available tracks.";
815 return;
816 }
817
818 const auto &tracks = mTracksMetadata.value(trackType);
819 if (streamNumber > tracks.count()) {
820 qCWarning(lcMediaPlayer) << "Trying to set a active track that does not exist.";
821 return;
822 }
823
824 // in case of < 0 deselect tracktype
825 if (streamNumber < 0) {
826 disableTrack(trackType);
827 return;
828 }
829
830 const auto currentTrack = activeTrack(trackType);
831 if (streamNumber == currentTrack) {
832 return;
833 }
834
835 if (trackType == TrackType::VideoStream && !mIsVideoTrackEnabled) {
836 // enable video stream
837 mMediaPlayer->setDisplay(mVideoOutput->surfaceTexture());
838 mIsVideoTrackEnabled = true;
839 }
840
841 if (trackType == TrackType::AudioStream && !mIsAudioTrackEnabled) {
842 // enable audio stream
843 mMediaPlayer->unblockAudio();
844 mMediaPlayer->setMuted(false);
845 mIsAudioTrackEnabled = true;
846 }
847
848 if (trackType == TrackType::SubtitleStream) {
849 // subtitles and timedtext tracks can be selected at the same time so deselect both before
850 // selecting a new one
851 disableTrack(TrackType::SubtitleStream);
852 }
853
854 const auto &trackInfo = tracks.at(streamNumber);
855 const auto &trackNumber = trackInfo.androidTrackNumber();
856 mMediaPlayer->selectTrack(trackNumber);
857
858 Q_EMIT activeTracksChanged();
859}
860
861void QAndroidMediaPlayer::positionChanged(qint64 position)
862{
863 QPlatformMediaPlayer::positionChanged(position);
864}
865
866void QAndroidMediaPlayer::durationChanged(qint64 duration)
867{
868 QPlatformMediaPlayer::durationChanged(duration);
869}
870
871void QAndroidMediaPlayer::onVideoOutputReady(bool ready)
872{
873 if ((mMediaPlayer->display() == 0) && mVideoOutput && ready)
874 mMediaPlayer->setDisplay(mVideoOutput->surfaceTexture());
875
876 flushPendingStates();
877}
878
879void QAndroidMediaPlayer::setMediaStatus(QMediaPlayer::MediaStatus status)
880{
881 mediaStatusChanged(status);
882
883 if (status == QMediaPlayer::NoMedia || status == QMediaPlayer::InvalidMedia) {
884 Q_EMIT durationChanged(0);
885 Q_EMIT metaDataChanged();
886 setAudioAvailable(false);
887 setVideoAvailable(false);
888 }
889
890 if (status == QMediaPlayer::EndOfMedia)
891 Q_EMIT positionChanged(position());
892
893 updateBufferStatus();
894}
895
896void QAndroidMediaPlayer::setAudioAvailable(bool available)
897{
898 if (mAudioAvailable == available)
899 return;
900
901 mAudioAvailable = available;
902 Q_EMIT audioAvailableChanged(mAudioAvailable);
903}
904
905void QAndroidMediaPlayer::setVideoAvailable(bool available)
906{
907 if (mVideoAvailable == available)
908 return;
909
910 if (!available)
911 mVideoSize = QSize();
912
913 mVideoAvailable = available;
914 Q_EMIT videoAvailableChanged(mVideoAvailable);
915}
916
917void QAndroidMediaPlayer::resetBufferingProgress()
918{
919 mBuffering = false;
920 mBufferPercent = 0;
921 mAvailablePlaybackRange = QMediaTimeRange();
922}
923
924void QAndroidMediaPlayer::flushPendingStates()
925{
926 if (mPendingSetMedia) {
927 setMedia(mMediaContent, 0);
928 mPendingSetMedia = false;
929 return;
930 }
931
932 const int newState = mPendingState;
933 mPendingState = -1;
934
935 if (mPendingPosition != -1)
936 setPosition(mPendingPosition);
937 if (mPendingVolume >= 0)
938 setVolume(mPendingVolume);
939 if (mPendingMute != -1)
940 setMuted((mPendingMute == 1));
941
942 switch (newState) {
943 case QMediaPlayer::PlayingState:
944 play();
945 break;
946 case QMediaPlayer::PausedState:
947 pause();
948 break;
949 case QMediaPlayer::StoppedState:
950 stop();
951 break;
952 default:
953 break;
954 }
955}
956
957void QAndroidMediaPlayer::updateBufferStatus()
958{
959 const auto &status = mediaStatus();
960 bool bufferFilled = (status == QMediaPlayer::BufferedMedia || status == QMediaPlayer::BufferingMedia);
961
962 if (mBufferFilled != bufferFilled)
963 mBufferFilled = bufferFilled;
964
965 Q_EMIT bufferProgressChanged(bufferProgress());
966}
967
968void QAndroidMediaPlayer::updateTrackInfo()
969{
970 const auto &androidTracksInfo = mMediaPlayer->tracksInfo();
971
972 // prepare mTracksMetadata
973 mTracksMetadata[TrackType::VideoStream] = QList<QAndroidMetaData>();
974 mTracksMetadata[TrackType::AudioStream] = QList<QAndroidMetaData>();
975 mTracksMetadata[TrackType::SubtitleStream] = QList<QAndroidMetaData>();
976
977 for (const auto &androidTrackInfo : androidTracksInfo) {
978
979 const auto mediaPlayerType = convertTrackType(androidTrackInfo.trackType);
980 if (!mediaPlayerType)
981 continue;
982
983 auto &tracks = mTracksMetadata[*mediaPlayerType];
984
985 const QAndroidMetaData metadata(*mediaPlayerType, androidTrackInfo.trackType,
986 androidTrackInfo.trackNumber, androidTrackInfo.mimeType,
987 androidTrackInfo.language);
988 tracks.append(metadata);
989 }
990
991 Q_EMIT tracksChanged();
992}
993
994QT_END_NAMESPACE
995
996#include "moc_qandroidmediaplayer_p.cpp"
void deselectTrack(int trackNumber)
void selectTrack(int trackNumber)
void setDisplay(AndroidSurfaceTexture *surfaceTexture)
int activeTrack(TrackType trackType)
void setVolume(int volume)
bool isAudioAvailable() const override
void setPlaybackRate(qreal rate) override
const QIODevice * mediaStream() const override
qreal playbackRate() const override
qint64 duration() const override
QMediaMetaData metaData() const override
int activeTrack(TrackType trackType) override
bool isVideoAvailable() const override
int trackCount(TrackType trackType) override
qint64 position() const override
float bufferProgress() const override
void setMedia(const QUrl &mediaContent, QIODevice *stream) override
QUrl media() const override
void setActiveTrack(TrackType trackType, int streamNumber) override
void setPosition(qint64 position) override
QMediaMetaData trackMetaData(TrackType trackType, int streamNumber) override
QMediaTimeRange availablePlaybackRanges() const override
AndroidSurfaceTexture * surfaceTexture() override
\inmodule QtCore
Definition qsize.h:27
StateChangeNotifier(QAndroidMediaPlayer *mp)
Combined button and popup list for selecting options.
std::optional< TrackType > convertTrackType(AndroidMediaPlayer::TrackType type)
#define qCWarning(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)