6#include <avfvideosink_p.h>
7#include <common/avfmetadata_p.h>
8#include <mediaplayer/avfvideorenderercontrol_p.h>
10#include <QtMultimedia/qaudiooutput.h>
11#include <QtMultimedia/private/qplatformaudiooutput_p.h>
12#if defined(Q_OS_IOS) || defined(Q_OS_VISIONOS) || defined(Q_OS_TVOS)
13# include <QtMultimedia/private/qavaudiosessionmanager_p.h>
16#include <QtCore/qdir.h>
17#include <QtCore/qfileinfo.h>
18#include <QtCore/qmath.h>
19#include <QtCore/qmimedatabase.h>
20#include <QtCore/qmutex.h>
21#include <QtCore/qpointer.h>
22#include <QtCore/qthread.h>
23#include <QtCore/private/qexpected_p.h>
27#import <AVFoundation/AVFoundation.h>
32static NSString*
const AVF_TRACKS_KEY = @
"tracks";
53@interface AVFMediaPlayerObserver : NSObject<AVAssetResourceLoaderDelegate>
55@property (readonly, getter=player) AVPlayer* m_player;
56@property (readonly, getter=playerItem) AVPlayerItem* m_playerItem;
57@property (readonly, getter=playerLayer) AVPlayerLayer* m_playerLayer;
58@property (retain) AVPlayerItemTrack *videoTrack;
60- (AVFMediaPlayerObserver *) initWithMediaPlayerSession:(AVFMediaPlayer *)session;
61- (
void) setURL:(NSURL *)url mimeType:(NSString *)mimeType;
63- (
void) prepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys;
64- (
void) assetFailedToPrepareForPlayback:(NSError *)error;
65- (
void) playerItemDidReachEnd:(NSNotification *)notification;
66- (
void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
67 change:(NSDictionary *)change context:(
void *)context;
69- (
void) notifySeekComplete;
71- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest;
76struct GuardedPlatformPlayer
81 explicit operator
bool()
const
83 std::lock_guard guard(mutex);
87 struct not_a_platform_player_t
91 template <
typename Functor>
92 auto withPlatformPlayer(Functor &&f)
93 -> q23::expected<std::invoke_result_t<Functor, AVFMediaPlayer *>,
94 not_a_platform_player_t>
96 std::unique_lock guard(mutex);
98 return q23::unexpected{ not_a_platform_player_t{} };
99 if constexpr (std::is_void_v<std::invoke_result_t<Functor, AVFMediaPlayer *>>) {
107 template <
typename Functor>
108 void invokeWithPlatformPlayer(Functor f)
110 std::unique_lock guard(mutex);
114 if (player->thread()->isCurrentThread()) {
118 QMetaObject::invokeMethod(player, [f = std::move(f), player = player]() {
126 std::lock_guard<QMutex> guard(mutex);
132#if defined(Q_OS_IOS) || defined(Q_OS_VISIONOS) || defined(Q_OS_TVOS)
133using ActivationToken = QtMultimediaPrivate::QAVAudioSessionManager::ActivationToken;
136@implementation AVFMediaPlayerObserver {
138 GuardedPlatformPlayer m_platformPlayer;
140 AVPlayerItem *m_playerItem;
141 AVPlayerLayer *m_playerLayer;
143 BOOL m_bufferIsLikelyToKeepUp;
145 NSString *m_mimeType;
146#if defined(Q_OS_IOS) || defined(Q_OS_VISIONOS) || defined(Q_OS_TVOS)
147 std::optional<ActivationToken> m_sessionActivation;
151@synthesize m_player, m_playerItem, m_playerLayer;
153- (AVFMediaPlayerObserver *) initWithMediaPlayerSession:(AVFMediaPlayer *)session
155 if (!(self = [super init]))
157 m_platformPlayer.player = session;
158 m_bufferIsLikelyToKeepUp = FALSE;
160 m_playerLayer = [AVPlayerLayer playerLayerWithPlayer:nil];
161 [m_playerLayer retain];
162 m_playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
163 m_playerLayer.anchorPoint = CGPointMake(0.0f, 0.0f);
167- (
void) setURL:(NSURL *)url mimeType:(NSString *)mimeType
169 [m_mimeType release];
170 m_mimeType = [mimeType retain];
182 __block BOOL isAccessing = [m_URL startAccessingSecurityScopedResource];
183 __block AVURLAsset *asset = [[AVURLAsset URLAssetWithURL:m_URL options:nil] retain];
184 [asset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()];
186 __block NSArray *requestedKeys = [@[ AVF_TRACKS_KEY, AVF_PLAYABLE_KEY ] retain];
188 __block AVFMediaPlayerObserver *blockSelf = [self retain];
191 [asset loadValuesAsynchronouslyForKeys:requestedKeys
193 dispatch_async(dispatch_get_main_queue(), ^{
195 [m_URL stopAccessingSecurityScopedResource];
196 [blockSelf prepareToPlayAsset:asset withKeys:requestedKeys];
198 [requestedKeys release];
208 [m_playerItem removeObserver:self forKeyPath:@
"presentationSize"];
209 [m_playerItem removeObserver:self forKeyPath:AVF_STATUS_KEY];
210 [m_playerItem removeObserver:self forKeyPath:AVF_BUFFER_LIKELY_KEEP_UP_KEY];
211 [m_playerItem removeObserver:self forKeyPath:AVF_TRACKS_KEY];
213 [[NSNotificationCenter defaultCenter] removeObserver:self
214 name:AVPlayerItemDidPlayToEndTimeNotification
215 object:m_playerItem];
216 m_playerItem =
nullptr;
219 [m_player setRate:0.0];
220 [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_DURATION_KEY];
221 [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_KEY];
222 [m_player removeObserver:self forKeyPath:AVF_RATE_KEY];
223 [m_player replaceCurrentItemWithPlayerItem:nil];
229 AVPlayer *player = m_player;
231 dispatch_async(dispatch_get_main_queue(), ^{
236 m_playerLayer.player = nil;
237#if defined(Q_OS_IOS) || defined(Q_OS_VISIONOS) || defined(Q_OS_TVOS)
238 m_sessionActivation.reset();
242- (
void) prepareToPlayAsset:(AVURLAsset *)asset
243 withKeys:(NSArray *)requestedKeys
245 if (!m_platformPlayer)
248#if defined(Q_OS_IOS) || defined(Q_OS_VISIONOS) || defined(Q_OS_TVOS)
249 auto sessionActivation = QtMultimediaPrivate::QAVAudioSessionManager::instance()->activate({
251 AVAudioSessionCategoryOptionMixWithOthers,
256 for (NSString *thisKey in requestedKeys)
258 NSError *error = nil;
259 AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
261 qDebug() << Q_FUNC_INFO << [thisKey UTF8String] <<
" status: " << keyStatus;
263 if (keyStatus == AVKeyValueStatusFailed)
265 [self assetFailedToPrepareForPlayback:error];
272 qDebug() << Q_FUNC_INFO <<
"isPlayable: " << [asset isPlayable];
275 qWarning() <<
"Asset reported to be not playable. Playback of this asset may not be possible.";
286 m_playerItem = [AVPlayerItem playerItemWithAsset:asset];
288 qWarning() <<
"Failed to create player item";
290 NSString *localizedDescription = NSLocalizedString(@
"Item cannot be played", @
"Item cannot be played description");
291 NSString *localizedFailureReason = NSLocalizedString(@
"The assets tracks were loaded, but couldn't create player item.", @
"Item cannot be played failure reason");
292 NSDictionary *errorDict = @{
293 NSLocalizedDescriptionKey : localizedDescription,
294 NSLocalizedFailureReasonErrorKey : localizedFailureReason,
296 NSError *assetCannotBePlayedError = [NSError errorWithDomain:@
"StitchedStreamPlayer" code:0 userInfo:errorDict];
298 [self assetFailedToPrepareForPlayback:assetCannotBePlayedError];
303 [m_playerItem addObserver:self
304 forKeyPath:AVF_STATUS_KEY
305 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
306 context:AVFMediaPlayerObserverStatusObservationContext];
308 [m_playerItem addObserver:self
309 forKeyPath:@
"presentationSize"
310 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
311 context:AVFMediaPlayerObserverPresentationSizeContext];
313 [m_playerItem addObserver:self
314 forKeyPath:AVF_BUFFER_LIKELY_KEEP_UP_KEY
315 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
316 context:AVFMediaPlayerObserverBufferLikelyToKeepUpContext];
318 [m_playerItem addObserver:self
319 forKeyPath:AVF_TRACKS_KEY
320 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
321 context:AVFMediaPlayerObserverTracksContext];
325 [[NSNotificationCenter defaultCenter] addObserver:self
326 selector:@selector(playerItemDidReachEnd:)
327 name:AVPlayerItemDidPlayToEndTimeNotification
328 object:m_playerItem];
331 m_player = [AVPlayer playerWithPlayerItem:m_playerItem];
336 m_platformPlayer.withPlatformPlayer([&](AVFMediaPlayer *player) {
337 auto *audioOutput = player->m_audioOutput;
338 m_player.volume = (audioOutput ? audioOutput->volume : 1.);
339 m_player.muted = (audioOutput ? audioOutput->muted :
true);
340 player->updateAudioOutputDevice();
345 m_playerLayer.player = m_player;
350 [m_player addObserver:self
351 forKeyPath:AVF_CURRENT_ITEM_KEY
352 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
353 context:AVFMediaPlayerObserverCurrentItemObservationContext];
356 [m_player addObserver:self
357 forKeyPath:AVF_RATE_KEY
358 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
359 context:AVFMediaPlayerObserverRateObservationContext];
362 [m_player addObserver:self
363 forKeyPath:AVF_CURRENT_ITEM_DURATION_KEY
365 context:AVFMediaPlayerObserverCurrentItemDurationObservationContext];
366#if defined(Q_OS_IOS) || defined(Q_OS_VISIONOS) || defined(Q_OS_TVOS)
367 m_sessionActivation = std::move(sessionActivation);
371-(
void) assetFailedToPrepareForPlayback:(NSError *)error
373 QMediaPlayer::Error errorCode = QMediaPlayer::FormatError;
375 NSError *underlyingError = error.userInfo[NSUnderlyingErrorKey];
376 if (underlyingError && ![underlyingError.domain isEqualToString:AVFoundationErrorDomain])
377 errorCode = QMediaPlayer::ResourceError;
379 m_platformPlayer.invokeWithPlatformPlayer([errorCode](AVFMediaPlayer *platformPlayer) {
380 platformPlayer->processMediaLoadError(errorCode);
384 qDebug() << Q_FUNC_INFO;
385 qDebug() << [[error localizedDescription] UTF8String];
386 qDebug() << [[error localizedFailureReason] UTF8String];
387 qDebug() << [[error localizedRecoverySuggestion] UTF8String];
391- (
void) playerItemDidReachEnd:(NSNotification *)notification
393 Q_UNUSED(notification);
395 m_platformPlayer.invokeWithPlatformPlayer([](AVFMediaPlayer *platformPlayer) {
396 platformPlayer->processEOS();
400- (
void) observeValueForKeyPath:(NSString*) path
402 change:(NSDictionary*)change
403 context:(
void*)context
406 if (context == AVFMediaPlayerObserverStatusObservationContext)
408 AVPlayerStatus status = (AVPlayerStatus)[[change objectForKey:NSKeyValueChangeNewKey] integerValue];
413 case AVPlayerStatusUnknown:
419 case AVPlayerStatusReadyToPlay:
425 m_platformPlayer.invokeWithPlatformPlayer([](AVFMediaPlayer *platformPlayer) {
426 platformPlayer->processLoadStateChange();
431 case AVPlayerStatusFailed:
433 AVPlayerItem *playerItem =
static_cast<AVPlayerItem*>(object);
434 [self assetFailedToPrepareForPlayback:playerItem.error];
436 m_platformPlayer.invokeWithPlatformPlayer([](AVFMediaPlayer *platformPlayer) {
437 platformPlayer->processLoadStateChange();
442 }
else if (context == AVFMediaPlayerObserverPresentationSizeContext) {
443 QSize size(m_playerItem.presentationSize.width, m_playerItem.presentationSize.height);
444 m_platformPlayer.invokeWithPlatformPlayer([size](AVFMediaPlayer *platformPlayer) {
445 platformPlayer->nativeSizeChanged(size);
447 }
else if (context == AVFMediaPlayerObserverBufferLikelyToKeepUpContext)
449 const bool isPlaybackLikelyToKeepUp = [m_playerItem isPlaybackLikelyToKeepUp];
450 if (isPlaybackLikelyToKeepUp != m_bufferIsLikelyToKeepUp) {
451 m_bufferIsLikelyToKeepUp = isPlaybackLikelyToKeepUp;
452 int bufferProgress = isPlaybackLikelyToKeepUp ? 100 : 0;
454 m_platformPlayer.invokeWithPlatformPlayer(
455 [bufferProgress](AVFMediaPlayer *platformPlayer) {
456 platformPlayer->processBufferStateChange(bufferProgress);
460 else if (context == AVFMediaPlayerObserverTracksContext)
462 m_platformPlayer.invokeWithPlatformPlayer([](AVFMediaPlayer *platformPlayer) {
463 platformPlayer->updateTracks();
467 else if (context == AVFMediaPlayerObserverRateObservationContext) {
473 else if (context == AVFMediaPlayerObserverCurrentItemObservationContext) {
474 AVPlayerItem *newPlayerItem = [change objectForKey:NSKeyValueChangeNewKey];
475 if (m_playerItem != newPlayerItem)
476 m_playerItem = newPlayerItem;
477 }
else if (context == AVFMediaPlayerObserverCurrentItemDurationObservationContext) {
478 const CMTime time = [m_playerItem duration];
479 const qint64 dur =
static_cast<qint64>(
float(time.value) /
float(time.timescale) * 1000.0f);
481 m_platformPlayer.invokeWithPlatformPlayer([dur](AVFMediaPlayer *platformPlayer) {
482 platformPlayer->processDurationChange(dur);
485 [super observeValueForKeyPath:path ofObject:object change:change context:context];
492 qDebug() << Q_FUNC_INFO;
494 m_platformPlayer.clear();
497- (
void)notifySeekComplete
499 m_platformPlayer.withPlatformPlayer([](AVFMediaPlayer *player) {
500 player->seekCompleted();
507 qDebug() << Q_FUNC_INFO;
511 m_platformPlayer.clear();
517 [m_mimeType release];
518 [m_playerLayer release];
521 self.videoTrack = nil;
525- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
527 Q_UNUSED(resourceLoader);
529 if (![loadingRequest.request.URL.scheme isEqualToString:@
"iodevice"])
532 auto result = m_platformPlayer.withPlatformPlayer([&](AVFMediaPlayer *platformPlayer) {
533 QIODevice *device = platformPlayer->mediaStream();
537 device->seek(loadingRequest.dataRequest.requestedOffset);
538 if (loadingRequest.contentInformationRequest) {
539 loadingRequest.contentInformationRequest.contentType = m_mimeType;
540 loadingRequest.contentInformationRequest.contentLength = device->size();
541 loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;
544 if (loadingRequest.dataRequest) {
545 NSInteger requestedLength = loadingRequest.dataRequest.requestedLength;
546 int maxBytes = qMin(32 * 1024,
int(requestedLength));
548 buffer.resize(maxBytes);
550 NSInteger submitted = 0;
551 while (submitted < requestedLength) {
552 qint64 len = device->read(buffer.data(), maxBytes);
556 [loadingRequest.dataRequest respondWithData:buffer.toNSData()];
561 [loadingRequest finishLoading];
567 return result.value_or(NO);
571AVFMediaPlayer::AVFMediaPlayer(QMediaPlayer *player)
573 QPlatformMediaPlayer(player),
574 m_mediaStream(
nullptr),
576 m_requestedPosition(-1),
580 m_observer = [[AVFMediaPlayerObserver alloc] initWithMediaPlayerSession:
this];
581 connect(&m_playbackTimer, &QTimer::timeout,
this, &AVFMediaPlayer::processPositionChange);
582 setVideoOutput(
new AVFVideoRendererControl(
this));
588 qDebug() << Q_FUNC_INFO;
593 [m_observer unloadMedia];
596 [m_observer clearSession];
597 [m_observer release];
602 m_videoSink = sink ?
static_cast<
AVFVideoSink *>(sink->platformVideoSink()):
nullptr;
609 qDebug() << Q_FUNC_INFO << output;
612 if (m_videoOutput == output)
617 m_videoOutput->setLayer(
nullptr);
620 m_videoOutput = output;
622 if (m_videoOutput && state() != QMediaPlayer::StoppedState)
623 m_videoOutput->setLayer([m_observer playerLayer]);
629 qDebug() << Q_FUNC_INFO;
631 AVAsset *currentAsset = [[m_observer playerItem] asset];
642 return m_mediaStream;
645static void setURL(AVFMediaPlayerObserver *observer,
const QUrl &url,
const QString &mimeType = QString())
647 QUrl resolvedUrl = url;
649 if (url.isLocalFile() && !QDir::isAbsolutePath(url.path()))
650 resolvedUrl = QUrl::fromLocalFile(QFileInfo(url.path()).absoluteFilePath());
651 NSURL *nsurl = resolvedUrl.toNSURL();
652 [observer setURL:nsurl mimeType:[NSString stringWithUTF8String:mimeType.toLatin1().constData()]];
659 qDebug() << Q_FUNC_INFO << content.request().url();
662 [m_observer unloadMedia];
664 m_resources = content;
667 m_requestedPosition = -1;
668 orientationChanged(QtVideo::Rotation::None,
false);
669 positionChanged(position());
670 if (m_duration != 0) {
674 if (!m_metaData.isEmpty()) {
678 resetBufferProgress();
679 for (size_t i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
681 nativeTracks[i].clear();
685 if (!m_mediaStream && content.isEmpty()) {
686 seekableChanged(
false);
687 audioAvailableChanged(
false);
688 videoAvailableChanged(
false);
690 mediaStatusChanged(QMediaPlayer::NoMedia);
691 stateChanged(QMediaPlayer::StoppedState);
696 mediaStatusChanged(QMediaPlayer::LoadingMedia);
701 if (m_mediaStream->size())
706 setURL(m_observer, m_resources);
709 stateChanged(QMediaPlayer::StoppedState);
714 AVPlayerItem *playerItem = [m_observer playerItem];
716 if (m_requestedPosition != -1)
717 return m_requestedPosition;
722 CMTime time = [playerItem currentTime];
723 return static_cast<quint64>(
float(time.value) /
float(time.timescale) * 1000.0f);
729 qDebug() << Q_FUNC_INFO;
737 qDebug() << Q_FUNC_INFO;
739 return m_bufferProgress/100.;
744 AVPlayerItem *playerItem = [m_observer playerItem];
749 if (state() == QMediaPlayer::StoppedState)
752 QMediaTimeRange timeRanges;
754 NSArray *ranges = [playerItem loadedTimeRanges];
755 for (NSValue *timeRange in ranges) {
756 CMTimeRange currentTimeRange = [timeRange CMTimeRangeValue];
757 qint64 startTime = qint64(
float(currentTimeRange.start.value) / currentTimeRange.start.timescale * 1000.0);
758 timeRanges.addInterval(startTime, startTime + qint64(
float(currentTimeRange.duration.value) / currentTimeRange.duration.timescale * 1000.0));
770 if (m_audioOutput == output)
773 m_audioOutput->q->disconnect(
this);
774 m_audioOutput = output;
782 setMuted(m_audioOutput ? m_audioOutput->muted :
true);
783 setVolume(m_audioOutput ? m_audioOutput->volume : 1.);
794 qDebug() << Q_FUNC_INFO << rate;
797 if (QtPrivate::fuzzyCompare(m_rate, rate))
802 AVPlayer *player = [m_observer player];
803 if (player && state() == QMediaPlayer::PlayingState)
804 [player setRate:m_rate];
806 playbackRateChanged(m_rate);
812 qDebug() << Q_FUNC_INFO << pos;
815 if (pos == position())
818 AVPlayerItem *playerItem = [m_observer playerItem];
820 m_requestedPosition = pos;
821 positionChanged(m_requestedPosition);
826 if (m_requestedPosition != -1) {
827 m_requestedPosition = -1;
828 positionChanged(position());
833 pos = qMax(qint64(0), pos);
835 pos = qMin(pos, duration());
836 m_requestedPosition = pos;
838 CMTime newTime = [playerItem currentTime];
839 newTime.value = (pos / 1000.0f) * newTime.timescale;
840 AVFMediaPlayerObserver *observer = m_observer;
841 [playerItem seekToTime:newTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero
842 completionHandler:^(BOOL finished) {
844 [observer notifySeekComplete];
847 positionChanged(pos);
850 if (mediaStatus() == QMediaPlayer::EndOfMedia) {
851 QMediaPlayer::MediaStatus newMediaStatus = (state() == QMediaPlayer::PausedState)
852 ? QMediaPlayer::BufferedMedia
853 : QMediaPlayer::LoadedMedia;
854 mediaStatusChanged(newMediaStatus);
861 qDebug() << Q_FUNC_INFO <<
"currently: " << state();
864 if (mediaStatus() == QMediaPlayer::NoMedia || mediaStatus() == QMediaPlayer::InvalidMedia)
867 if (state() == QMediaPlayer::PlayingState)
870 if (state() != QMediaPlayer::PausedState)
873 if (m_videoOutput && m_videoSink)
874 m_videoOutput->setLayer([m_observer playerLayer]);
877 if (mediaStatus() == QMediaPlayer::EndOfMedia)
880 if (mediaStatus() == QMediaPlayer::LoadedMedia
881 || mediaStatus() == QMediaPlayer::BufferedMedia) {
883 [[m_observer player] setRate:m_rate];
888 stateChanged(QMediaPlayer::PlayingState);
889 m_playbackTimer.start(100);
895 qDebug() << Q_FUNC_INFO <<
"currently: " << state();
898 if (mediaStatus() == QMediaPlayer::NoMedia || mediaStatus() == QMediaPlayer::InvalidMedia)
901 if (state() == QMediaPlayer::PausedState)
904 stateChanged(QMediaPlayer::PausedState);
906 if (m_videoOutput && m_videoSink)
907 m_videoOutput->setLayer([m_observer playerLayer]);
909 [[m_observer player] pause];
912 if (mediaStatus() == QMediaPlayer::EndOfMedia)
915 positionChanged(position());
916 m_playbackTimer.stop();
922 qDebug() << Q_FUNC_INFO <<
"currently: " << state();
925 if (state() == QMediaPlayer::StoppedState && mediaStatus() != QMediaPlayer::EndOfMedia)
929 [[m_observer player] pause];
933 m_videoOutput->setLayer(
nullptr);
935 resetBufferProgress();
937 if (mediaStatus() == QMediaPlayer::BufferedMedia || mediaStatus() == QMediaPlayer::EndOfMedia)
938 mediaStatusChanged(QMediaPlayer::LoadedMedia);
940 stateChanged(QMediaPlayer::StoppedState);
941 m_playbackTimer.stop();
947 qDebug() << Q_FUNC_INFO << volume;
950 AVPlayer *player = [m_observer player];
952 player.volume = volume;
958 qDebug() << Q_FUNC_INFO << muted;
961 AVPlayer *player = [m_observer player];
963 player.muted = muted;
969 AVPlayer *player = [m_observer player];
973 if (!m_audioOutput || m_audioOutput->device.id().isEmpty()) {
976 player.audioOutputDeviceUniqueID = nil;
978 NSString *str = QString::fromUtf8(m_audioOutput->device.id()).toNSString();
979 player.audioOutputDeviceUniqueID = str;
987 positionChanged(duration());
989 [[m_observer player] setRate:m_rate];
995 qDebug() << Q_FUNC_INFO;
997 positionChanged(position());
1000 m_videoOutput->setLayer(
nullptr);
1002 resetBufferProgress();
1004 stateChanged(QMediaPlayer::StoppedState);
1005 mediaStatusChanged(QMediaPlayer::EndOfMedia);
1010 AVPlayerStatus currentStatus = [[m_observer player] status];
1013 qDebug() << Q_FUNC_INFO << currentStatus <<
", " << mediaStatus() <<
", " << newState;
1016 if (mediaStatus() == QMediaPlayer::NoMedia)
1019 if (currentStatus == AVPlayerStatusReadyToPlay) {
1021 AVPlayerItem *playerItem = [m_observer playerItem];
1023 applyPitchCompensation(m_pitchCompensationEnabled);
1026 m_metaData = AVFMetaData::fromAsset(playerItem.asset);
1031 seekableChanged([[playerItem seekableTimeRanges] count] > 0);
1034 AVPlayerLayer *playerLayer = [m_observer playerLayer];
1035 if (m_observer.videoTrack && playerLayer) {
1036 if (!playerLayer.bounds.size.width || !playerLayer.bounds.size.height) {
1037 playerLayer.bounds = CGRectMake(0.0f, 0.0f,
1038 m_observer.videoTrack.assetTrack.naturalSize.width,
1039 m_observer.videoTrack.assetTrack.naturalSize.height);
1043 if (m_requestedPosition != -1)
1044 setPosition(m_requestedPosition);
1047 QMediaPlayer::MediaStatus newStatus = (newState != QMediaPlayer::StoppedState)
1048 ? QMediaPlayer::BufferedMedia
1049 : QMediaPlayer::LoadedMedia;
1051 if (newStatus != mediaStatus()) {
1052 if (newStatus == QMediaPlayer::BufferedMedia
1053 && mediaStatus() == QMediaPlayer::LoadingMedia) {
1055 mediaStatusChanged(QMediaPlayer::LoadedMedia);
1056 mediaStatusChanged(QMediaPlayer::BufferingMedia);
1057 }
else if (newStatus == QMediaPlayer::BufferedMedia
1058 && mediaStatus() == QMediaPlayer::LoadedMedia) {
1059 mediaStatusChanged(QMediaPlayer::BufferingMedia);
1061 mediaStatusChanged(newStatus);
1065 if (newState == QMediaPlayer::PlayingState && [m_observer player]) {
1067 [[m_observer player] setRate:m_rate];
1068 m_playbackTimer.start();
1081 stateChanged(QMediaPlayer::StoppedState);
1086 if (state() == QMediaPlayer::StoppedState)
1089 if (bufferProgress == m_bufferProgress)
1092 auto status = mediaStatus();
1094 if (!bufferProgress) {
1095 status = QMediaPlayer::StalledMedia;
1096 }
else if (status == QMediaPlayer::StalledMedia) {
1097 status = QMediaPlayer::BufferedMedia;
1099 if (state() == QMediaPlayer::PlayingState) {
1100 [[m_observer player] setRate:m_rate];
1101 m_playbackTimer.start();
1105 mediaStatusChanged(status);
1107 m_bufferProgress = bufferProgress;
1108 bufferProgressChanged(bufferProgress / 100.);
1113 if (duration == m_duration)
1116 m_duration = duration;
1117 durationChanged(duration);
1122 if (state() == QMediaPlayer::StoppedState)
1125 positionChanged(position());
1130 if (m_requestedPosition != -1) {
1131 m_requestedPosition = -1;
1132 positionChanged(position());
1135 setInvalidMediaWithError(errorCode, tr(
"Failed to load media"));
1140 m_requestedPosition = -1;
1151 if (!m_resources.isEmpty())
1152 suffix = QFileInfo(m_resources.path()).suffix();
1153 if (suffix.isEmpty() && m_mediaStream)
1154 suffix = QMimeDatabase().mimeTypeForData(m_mediaStream).preferredSuffix();
1155 const QString url = QStringLiteral(
"iodevice:///iodevice.") + suffix;
1156 setURL(m_observer, QUrl(url), suffix);
1161 resetStream(
nullptr);
1166 bool firstLoad =
true;
1167 for (size_t i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
1168 if (tracks[i].count())
1171 nativeTracks[i].clear();
1173 bool hasAudio =
false;
1174 bool hasVideo =
false;
1175 AVPlayerItem *playerItem = [m_observer playerItem];
1178 NSArray *tracks = playerItem.tracks;
1179 for (AVPlayerItemTrack *track in tracks) {
1180 AVAssetTrack *assetTrack = track.assetTrack;
1182 std::optional<TrackType> qtTrack;
1183 if ([assetTrack.mediaType isEqualToString:AVMediaTypeAudio]) {
1184 qtTrack = TrackType::AudioStream;
1186 }
else if ([assetTrack.mediaType isEqualToString:AVMediaTypeVideo]) {
1187 qtTrack = TrackType::VideoStream;
1189 if (m_observer.videoTrack != track) {
1190 m_observer.videoTrack = track;
1191 bool isMirrored =
false;
1192 QtVideo::Rotation orientation = QtVideo::Rotation::None;
1193 videoOrientationForAssetTrack(assetTrack, orientation, isMirrored);
1194 orientationChanged(orientation, isMirrored);
1197 else if ([assetTrack.mediaType isEqualToString:AVMediaTypeSubtitle]) {
1198 qtTrack = TrackType::SubtitleStream;
1201 QMediaMetaData metaData = AVFMetaData::fromAssetTrack(assetTrack);
1202 this->tracks[*qtTrack].append(metaData);
1203 nativeTracks[*qtTrack].append(track);
1209 setActiveTrack(TrackType::SubtitleStream, -1);
1211 audioAvailableChanged(hasAudio);
1212 videoAvailableChanged(hasVideo);
1218 const auto &t = nativeTracks[type];
1219 if (type == TrackType::SubtitleStream) {
1222 AVPlayerItem *playerItem = m_observer.m_playerItem;
1224 AVAsset *asset = playerItem.asset;
1227 [asset loadMediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible
1228 completionHandler:[=](AVMediaSelectionGroup *group,
1232 auto *options = group.options;
1234 [playerItem selectMediaOption:options.firstObject inMediaSelectionGroup:group];
1238 for (
int i = 0; i < t.count(); ++i)
1239 t.at(i).enabled = (i == index);
1240 activeTracksChanged();
1245 const auto &t = nativeTracks[type];
1246 for (
int i = 0; i < t.count(); ++i)
1247 if (t.at(i).enabled)
1254 return nativeTracks[type].count();
1259 const auto &t = tracks[type];
1260 if (trackNumber < 0 || trackNumber >= t.count())
1261 return QMediaMetaData();
1262 return t.at(trackNumber);
1267 if (m_mediaStream) {
1272 m_mediaStream = stream;
1274 if (m_mediaStream) {
1282 AVPlayerItem *playerItem = [m_observer playerItem];
1285 playerItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmSpectral;
1287 playerItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmVarispeed;
1293 if (m_bufferProgress != 0) {
1294 m_bufferProgress = 0;
1295 bufferProgressChanged(0);
1303 m_videoSink->setNativeSize(size);
1306void AVFMediaPlayer::orientationChanged(QtVideo::Rotation rotation,
bool mirrored)
1311 m_videoOutput->setVideoRotation(rotation);
1316 QtVideo::Rotation &angle,
1319 angle = QtVideo::Rotation::None;
1322 CGAffineTransform transform = videoTrack.preferredTransform;
1323 if (CGAffineTransformIsIdentity(transform))
1327 qreal det = transform.a * transform.d - transform.b * transform.c;
1328 mirrored = (det < 0.0);
1332 qreal ra = mirrored ? -transform.a : transform.a;
1333 qreal rb = mirrored ? -transform.b : transform.b;
1335 qreal degrees = qRadiansToDegrees(qAtan2(rb, ra));
1339 if (QtPrivate::fuzzyCompare(degrees, qreal(90))
1340 || QtPrivate::fuzzyCompare(degrees, qreal(-270))) {
1341 angle = QtVideo::Rotation::Clockwise90;
1342 }
else if (QtPrivate::fuzzyCompare(degrees, qreal(-90))
1343 || QtPrivate::fuzzyCompare(degrees, qreal(270))) {
1344 angle = QtVideo::Rotation::Clockwise270;
1345 }
else if (QtPrivate::fuzzyCompare(degrees, qreal(180))
1346 || QtPrivate::fuzzyCompare(degrees, qreal(-180))) {
1347 angle = QtVideo::Rotation::Clockwise180;
1354 if (m_pitchCompensationEnabled == enabled)
1357 applyPitchCompensation(enabled);
1359 m_pitchCompensationEnabled = enabled;
1360 pitchCompensationChanged(enabled);
1365 return m_pitchCompensationEnabled;
1371 return QPlatformMediaPlayer::PitchCompensationAvailability::Available;
1374#include "moc_avfmediaplayer_p.cpp"
void setVideoMirrored(bool mirrored)
void setVideoSink(AVFVideoSink *sink)