5#include "wrappers/jni/androidmediaplayer_p.h"
6#include "common/qandroidvideooutput_p.h"
8#include "common/qandroidaudiooutput_p.h"
11#include <QtMultimedia/private/qplatformvideosink_p.h>
12#include <qloggingcategory.h>
16using TrackType = QPlatformMediaPlayer::TrackType;
28 ++mControl->mActiveStateChangeNotifiers;
33 if (--mControl->mActiveStateChangeNotifiers)
36 if (mPreviousMediaStatus != mControl->mediaStatus())
37 Q_EMIT mControl->mediaStatusChanged(mControl->mediaStatus());
39 if (mPreviousState != mControl->state())
40 Q_EMIT mControl->stateChanged(mControl->state());
45 QMediaPlayer::PlaybackState mPreviousState;
46 QMediaPlayer::MediaStatus mPreviousMediaStatus;
50 : QPlatformMediaPlayer(parent),
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);
75 disconnect(m_videoSink->platformVideoSink(),
nullptr,
this,
nullptr);
77 mMediaPlayer->disconnect();
84 if (mediaStatus() == QMediaPlayer::NoMedia)
100 if (mediaStatus() == QMediaPlayer::EndOfMedia)
110 return (mPendingPosition == -1) ? 0 : mPendingPosition;
118 const int seekPosition = (position > INT_MAX) ? INT_MAX : position;
121 if (seekPosition == currentPosition) {
124 mMediaPlayer->seekTo(seekPosition);
129 if (mediaStatus() == QMediaPlayer::EndOfMedia)
130 setMediaStatus(QMediaPlayer::LoadedMedia);
136 mPendingPosition = seekPosition;
138 mMediaPlayer->seekTo(seekPosition);
140 if (mPendingPosition != -1) {
141 mPendingPosition = -1;
145 Q_EMIT positionChanged(seekPosition);
157 mPendingVolume = volume;
174 mPendingMute = muted;
184 return QAndroidMetaData::extractMetadata(mMediaContent);
189 return mBufferFilled ? 1. : mBufferPercent;
194 return mAudioAvailable;
199 return mVideoAvailable;
204 return mAvailablePlaybackRange;
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());
216 mAvailablePlaybackRange = QMediaTimeRange();
224 return mCurrentPlaybackRate;
232 if (mCurrentPlaybackRate != rate) {
233 mCurrentPlaybackRate = rate;
234 mHasPendingPlaybackRate =
true;
235 Q_EMIT playbackRateChanged(rate);
240 if (mMediaPlayer->setPlaybackRate(rate)) {
241 mCurrentPlaybackRate = rate;
242 Q_EMIT playbackRateChanged(rate);
248 return mMediaContent;
261 mReloadingMedia = (mMediaContent == mediaContent) && !mPendingSetMedia;
263 if (!mReloadingMedia) {
264 mMediaContent = mediaContent;
265 mMediaStream = stream;
268 if (mediaContent.isEmpty()) {
269 setMediaStatus(QMediaPlayer::NoMedia);
271 if (mVideoOutput && !mVideoOutput->isReady()) {
274 mPendingSetMedia =
true;
278 if (mVideoSize.isValid() && mVideoOutput)
279 mVideoOutput->setVideoSize(mVideoSize);
284 mMediaPlayer->setDataSource(QNetworkRequest(mediaContent));
287 if (!mReloadingMedia)
288 setMediaStatus(QMediaPlayer::LoadingMedia);
291 resetBufferingProgress();
293 mReloadingMedia =
false;
298 if (m_videoSink == sink)
302 disconnect(m_videoSink->platformVideoSink(),
nullptr,
this,
nullptr);
312 mVideoOutput =
nullptr;
316 mVideoOutput =
new QAndroidTextureVideoOutput(sink,
this);
317 connect(mVideoOutput, &QAndroidTextureVideoOutput::readyChanged,
this,
319 connect(mMediaPlayer, &AndroidMediaPlayer::timedTextChanged, mVideoOutput,
320 &QAndroidTextureVideoOutput::setSubtitle);
322 if (mVideoOutput->isReady())
325 if (
auto *rhiSink = qobject_cast<QRhiVideoSink *>(m_videoSink->platformVideoSink()))
326 connect(rhiSink, &QRhiVideoSink::rhiChanged,
this, [&]()
332 if (m_audioOutput == output)
335 m_audioOutput->q->disconnect(
this);
336 m_audioOutput =
static_cast<QAndroidAudioOutput *>(output);
339 connect(m_audioOutput->q, &QAudioOutput::volumeChanged,
this, &QAndroidMediaPlayer::setVolume);
340 connect(m_audioOutput->q, &QAudioOutput::mutedChanged,
this, &
QAndroidMediaPlayer::setMuted);
348 mMediaPlayer->setAudioOutput(m_audioOutput->device.id());
358 if ((mState & AndroidMediaPlayer::Stopped) && !mMediaContent.isEmpty()) {
359 setMedia(mMediaContent, mMediaStream);
362 if (!mMediaContent.isEmpty())
363 stateChanged(QMediaPlayer::PlayingState);
369 mPendingState = QMediaPlayer::PlayingState;
374 mVideoOutput->start();
378 if (mHasPendingPlaybackRate) {
379 mHasPendingPlaybackRate =
false;
380 if (mMediaPlayer->setPlaybackRate(mCurrentPlaybackRate))
382 mCurrentPlaybackRate = mMediaPlayer->playbackRate();
383 Q_EMIT playbackRateChanged(mCurrentPlaybackRate);
392 if (mediaStatus() == QMediaPlayer::NoMedia)
397 stateChanged(QMediaPlayer::PausedState);
404 mPendingState = QMediaPlayer::PausedState;
409 setPosition(currentPosition);
418 stateChanged(QMediaPlayer::StoppedState);
426 mPendingState = QMediaPlayer::StoppedState;
430 if (mCurrentPlaybackRate != 1.)
432 mHasPendingPlaybackRate =
true;
454 mPendingState = state();
455 stateChanged(QMediaPlayer::PausedState);
456 setMediaStatus(QMediaPlayer::StalledMedia);
459 if (state() != QMediaPlayer::StoppedState)
460 flushPendingStates();
465 seekableChanged(
false);
468 Q_EMIT metaDataChanged();
478 QMediaPlayer::Error error = QMediaPlayer::ResourceError;
482 errorString = QLatin1String(
"Error:");
485 errorString = QLatin1String(
"Error: Server died");
486 error = QMediaPlayer::ResourceError;
489 errorString = QLatin1String(
"Error: Invalid state");
490 error = QMediaPlayer::ResourceError;
496 errorString += QLatin1String(
" (I/O operation failed)");
497 setInvalidMediaWithError(QMediaPlayer::NetworkError, errorString);
500 errorString += QLatin1String(
" (Malformed bitstream)");
501 setInvalidMediaWithError(QMediaPlayer::FormatError, errorString);
504 errorString += QLatin1String(
" (Unsupported media)");
505 setInvalidMediaWithError(QMediaPlayer::FormatError, errorString);
508 errorString += QLatin1String(
" (Timed out)");
511 errorString += QLatin1String(
" (Unable to start progressive playback')");
512 setInvalidMediaWithError(QMediaPlayer::FormatError, errorString);
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;
522 Q_EMIT QPlatformMediaPlayer::error(error, errorString);
529 mBuffering = percent != 100;
530 mBufferPercent = percent;
532 updateAvailablePlaybackRanges();
534 if (state() != QMediaPlayer::StoppedState)
535 setMediaStatus(mBuffering ? QMediaPlayer::BufferingMedia : QMediaPlayer::BufferedMedia);
537 updateBufferStatus();
542 QSize newSize(width, height);
544 if (width == 0 || height == 0 || newSize == mVideoSize)
547 setVideoAvailable(
true);
548 mVideoSize = newSize;
551 mVideoOutput->setVideoSize(mVideoSize);
570 case AndroidMediaPlayer::Preparing:
571 if (!mReloadingMedia)
572 setMediaStatus(QMediaPlayer::LoadingMedia);
574 case AndroidMediaPlayer::Prepared:
575 setMediaStatus(QMediaPlayer::LoadedMedia);
577 setMediaStatus(mBufferPercent == 100 ? QMediaPlayer::BufferedMedia
578 : QMediaPlayer::BufferingMedia);
580 onBufferingChanged(100);
583 Q_EMIT metaDataChanged();
584 setAudioAvailable(
true);
585 flushPendingStates();
588 stateChanged(QMediaPlayer::PlayingState);
590 setMediaStatus(mBufferPercent == 100 ? QMediaPlayer::BufferedMedia
591 : QMediaPlayer::BufferingMedia);
593 setMediaStatus(QMediaPlayer::BufferedMedia);
595 Q_EMIT positionChanged(position());
598 stateChanged(QMediaPlayer::PausedState);
599 if (mediaStatus() == QMediaPlayer::EndOfMedia) {
601 setMediaStatus(QMediaPlayer::BufferedMedia);
603 Q_EMIT positionChanged(position());
607 stateChanged(QMediaPlayer::StoppedState);
608 setMediaStatus(QMediaPlayer::InvalidMedia);
610 Q_EMIT positionChanged(0);
613 stateChanged(QMediaPlayer::StoppedState);
614 setMediaStatus(QMediaPlayer::LoadedMedia);
615 Q_EMIT positionChanged(0);
623 stateChanged(QMediaPlayer::StoppedState);
624 setMediaStatus(QMediaPlayer::EndOfMedia);
628 if (!mReloadingMedia) {
629 resetBufferingProgress();
630 mPendingPosition = -1;
631 mPendingSetMedia =
false;
634 Q_EMIT durationChanged(0);
635 Q_EMIT positionChanged(0);
637 setAudioAvailable(
false);
638 setVideoAvailable(
false);
639 seekableChanged(
true);
656 if (!mTracksMetadata.contains(trackType))
659 auto tracks = mTracksMetadata.value(trackType);
660 return tracks.count();
665 if (!mTracksMetadata.contains(trackType))
666 return QMediaMetaData();
668 auto tracks = mTracksMetadata.value(trackType);
669 if (tracks.count() < streamNumber)
670 return QMediaMetaData();
673 return static_cast<QMediaMetaData>(trackInfo);
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:
697 int trackNumber = androidTrackNumber;
699 int videoTrackCount = trackCount(TrackType::VideoStream);
700 if (trackNumber <= videoTrackCount)
703 trackNumber = trackNumber - videoTrackCount;
705 int audioTrackCount = trackCount(TrackType::AudioStream);
706 if (trackNumber <= audioTrackCount)
709 trackNumber = trackNumber - audioTrackCount;
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)
718 if (track.androidTrackType() == 4)
722 if (trackNumber <= timedTextCount)
725 trackNumber = trackNumber - timedTextCount;
727 if (trackNumber <= subtitleTextCount)
735 int androidTrackNumber = -1;
738 case TrackType::VideoStream: {
739 if (!mIsVideoTrackEnabled)
743 case TrackType::AudioStream: {
744 if (!mIsAudioTrackEnabled)
749 case TrackType::SubtitleStream: {
750 int timedTextSelectedTrack =
753 if (timedTextSelectedTrack > -1) {
754 androidTrackNumber = timedTextSelectedTrack;
758 int subtitleSelectedTrack =
760 if (subtitleSelectedTrack > -1) {
761 androidTrackNumber = subtitleSelectedTrack;
769 return convertTrackNumber(androidTrackNumber);
774 const auto track = activeTrack(trackType);
777 case TrackType::VideoStream: {
780 mIsVideoTrackEnabled =
false;
784 case TrackType::AudioStream: {
788 mIsAudioTrackEnabled =
false;
792 case TrackType::SubtitleStream: {
794 int subtitleSelectedTrack =
796 if (subtitleSelectedTrack > -1)
799 int timedTextSelectedTrack =
801 if (timedTextSelectedTrack > -1)
812 if (!mTracksMetadata.contains(trackType)) {
814 <<
"Trying to set a active track which type has no available tracks.";
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.";
825 if (streamNumber < 0) {
826 disableTrack(trackType);
830 const auto currentTrack = activeTrack(trackType);
831 if (streamNumber == currentTrack) {
835 if (trackType == TrackType::VideoStream && !mIsVideoTrackEnabled) {
838 mIsVideoTrackEnabled =
true;
841 if (trackType == TrackType::AudioStream && !mIsAudioTrackEnabled) {
845 mIsAudioTrackEnabled =
true;
848 if (trackType == TrackType::SubtitleStream) {
851 disableTrack(TrackType::SubtitleStream);
854 const auto &trackInfo = tracks.at(streamNumber);
855 const auto &trackNumber = trackInfo.androidTrackNumber();
858 Q_EMIT activeTracksChanged();
863 QPlatformMediaPlayer::positionChanged(position);
868 QPlatformMediaPlayer::durationChanged(duration);
873 if ((mMediaPlayer->display() == 0) && mVideoOutput && ready)
876 flushPendingStates();
881 mediaStatusChanged(status);
883 if (status == QMediaPlayer::NoMedia || status == QMediaPlayer::InvalidMedia) {
884 Q_EMIT durationChanged(0);
885 Q_EMIT metaDataChanged();
886 setAudioAvailable(
false);
887 setVideoAvailable(
false);
890 if (status == QMediaPlayer::EndOfMedia)
891 Q_EMIT positionChanged(position());
893 updateBufferStatus();
898 if (mAudioAvailable == available)
901 mAudioAvailable = available;
902 Q_EMIT audioAvailableChanged(mAudioAvailable);
907 if (mVideoAvailable == available)
911 mVideoSize = QSize();
913 mVideoAvailable = available;
914 Q_EMIT videoAvailableChanged(mVideoAvailable);
921 mAvailablePlaybackRange = QMediaTimeRange();
926 if (mPendingSetMedia) {
927 setMedia(mMediaContent, 0);
928 mPendingSetMedia =
false;
932 const int newState = mPendingState;
935 if (mPendingPosition != -1)
936 setPosition(mPendingPosition);
937 if (mPendingVolume >= 0)
938 setVolume(mPendingVolume);
939 if (mPendingMute != -1)
940 setMuted((mPendingMute == 1));
943 case QMediaPlayer::PlayingState:
946 case QMediaPlayer::PausedState:
949 case QMediaPlayer::StoppedState:
959 const auto &status = mediaStatus();
960 bool bufferFilled = (status == QMediaPlayer::BufferedMedia || status == QMediaPlayer::BufferingMedia);
962 if (mBufferFilled != bufferFilled)
963 mBufferFilled = bufferFilled;
970 const auto &androidTracksInfo = mMediaPlayer->tracksInfo();
973 mTracksMetadata[TrackType::VideoStream] = QList<QAndroidMetaData>();
974 mTracksMetadata[TrackType::AudioStream] = QList<QAndroidMetaData>();
975 mTracksMetadata[TrackType::SubtitleStream] = QList<QAndroidMetaData>();
977 for (
const auto &androidTrackInfo : androidTracksInfo) {
979 const auto mediaPlayerType = convertTrackType(androidTrackInfo.trackType);
980 if (!mediaPlayerType)
983 auto &tracks = mTracksMetadata[*mediaPlayerType];
985 const QAndroidMetaData metadata(*mediaPlayerType, androidTrackInfo.trackType,
986 androidTrackInfo.trackNumber, androidTrackInfo.mimeType,
987 androidTrackInfo.language);
988 tracks.append(metadata);
991 Q_EMIT tracksChanged();
996#include "moc_qandroidmediaplayer_p.cpp"
AndroidSurfaceTexture * surfaceTexture() override
bool shouldTextureBeUpdated() const
StateChangeNotifier(QAndroidMediaPlayer *mp)
Combined button and popup list for selecting options.
#define qCWarning(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)