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/qlocale.h>
19#include <QtCore/qmath.h>
20#include <QtCore/qmimedatabase.h>
21#include <QtCore/qmutex.h>
22#include <QtCore/qpointer.h>
23#include <QtCore/qthread.h>
24#include <QtCore/private/qexpected_p.h>
29#import <AVFoundation/AVFoundation.h>
34static NSString*
const AVF_TRACKS_KEY = @
"tracks";
55@interface AVFMediaPlayerObserver : NSObject<AVAssetResourceLoaderDelegate>
57@property (readonly, getter=player) AVPlayer* m_player;
58@property (readonly, getter=playerItem) AVPlayerItem* m_playerItem;
59@property (readonly, getter=playerLayer) AVPlayerLayer* m_playerLayer;
60@property (retain) AVPlayerItemTrack *videoTrack;
62- (AVFMediaPlayerObserver *) initWithMediaPlayerSession:(AVFMediaPlayer *)session;
63- (
void) setURL:(NSURL *)url mimeType:(NSString *)mimeType;
65- (
void) prepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys;
66- (
void) assetFailedToPrepareForPlayback:(NSError *)error;
67- (
void)loadSubtitleOptionsForAsset:(AVAsset *)asset generation:(
int)generation;
68- (
void) playerItemDidReachEnd:(NSNotification *)notification;
69- (
void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
70 change:(NSDictionary *)change context:(
void *)context;
72- (
void) notifySeekComplete;
74- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest;
79struct GuardedPlatformPlayer
84 explicit operator
bool()
const
86 std::lock_guard guard(mutex);
90 struct not_a_platform_player_t
94 template <
typename Functor>
95 auto withPlatformPlayer(Functor &&f)
96 -> q23::expected<std::invoke_result_t<Functor, AVFMediaPlayer *>,
97 not_a_platform_player_t>
99 std::unique_lock guard(mutex);
101 return q23::unexpected{ not_a_platform_player_t{} };
102 if constexpr (std::is_void_v<std::invoke_result_t<Functor, AVFMediaPlayer *>>) {
110 template <
typename Functor>
111 void invokeWithPlatformPlayer(Functor f)
113 std::unique_lock guard(mutex);
117 if (player->thread()->isCurrentThread()) {
121 QMetaObject::invokeMethod(player, [f = std::move(f), player = player]() {
129 std::lock_guard<QMutex> guard(mutex);
135#if defined(Q_OS_IOS) || defined(Q_OS_VISIONOS) || defined(Q_OS_TVOS)
136using ActivationToken = QtMultimediaPrivate::QAVAudioSessionManager::ActivationToken;
142bool isMachineGenerated([[maybe_unused]] AVMediaSelectionOption *option)
144#if QT_APPLE_SDK_EQUAL_OR_ABOVE(MACOS(26
), IOS(26
))
145 if (@available(macOS 26.0, iOS 26.0, *))
146 return [option hasMediaCharacteristic:AVMediaCharacteristicMachineGenerated];
152std::shared_ptr<T> retainObject(T *object)
154 return std::shared_ptr<T>((T *)[object retain], [](T *p) {
159QMediaMetaData subtitleMetaData(AVMediaSelectionOption *option)
161 QMediaMetaData metaData;
162 metaData.insert(QMediaMetaData::Title, QString::fromNSString(option.displayName));
163 if (option.extendedLanguageTag) {
164 const QLocale::Language language =
165 QLocale::codeToLanguage(QString::fromNSString(option.extendedLanguageTag));
166 if (language != QLocale::AnyLanguage)
167 metaData.insert(QMediaMetaData::Language, language);
173@implementation AVFMediaPlayerObserver {
175 GuardedPlatformPlayer m_platformPlayer;
177 AVPlayerItem *m_playerItem;
178 AVPlayerLayer *m_playerLayer;
180 BOOL m_bufferIsLikelyToKeepUp;
182 NSString *m_mimeType;
183#if defined(Q_OS_IOS) || defined(Q_OS_VISIONOS) || defined(Q_OS_TVOS)
184 std::optional<ActivationToken> m_sessionActivation;
188@synthesize m_player, m_playerItem, m_playerLayer;
190- (AVFMediaPlayerObserver *) initWithMediaPlayerSession:(AVFMediaPlayer *)session
192 if (!(self = [super init]))
194 m_platformPlayer.player = session;
195 m_bufferIsLikelyToKeepUp = FALSE;
197 m_playerLayer = [AVPlayerLayer playerLayerWithPlayer:nil];
198 [m_playerLayer retain];
199 m_playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
200 m_playerLayer.anchorPoint = CGPointMake(0.0f, 0.0f);
204- (
void) setURL:(NSURL *)url mimeType:(NSString *)mimeType
206 [m_mimeType release];
207 m_mimeType = [mimeType retain];
219 __block BOOL isAccessing = [m_URL startAccessingSecurityScopedResource];
220 __block AVURLAsset *asset = [[AVURLAsset URLAssetWithURL:m_URL options:nil] retain];
221 [asset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()];
223 __block NSArray *requestedKeys = [@[ AVF_TRACKS_KEY, AVF_PLAYABLE_KEY ] retain];
225 __block AVFMediaPlayerObserver *blockSelf = [self retain];
228 [asset loadValuesAsynchronouslyForKeys:requestedKeys
230 dispatch_async(dispatch_get_main_queue(), ^{
232 [m_URL stopAccessingSecurityScopedResource];
233 [blockSelf prepareToPlayAsset:asset withKeys:requestedKeys];
235 [requestedKeys release];
245 [m_playerItem removeObserver:self forKeyPath:@
"presentationSize"];
246 [m_playerItem removeObserver:self forKeyPath:AVF_STATUS_KEY];
247 [m_playerItem removeObserver:self forKeyPath:AVF_BUFFER_LIKELY_KEEP_UP_KEY];
248 [m_playerItem removeObserver:self forKeyPath:AVF_TRACKS_KEY];
250 [[NSNotificationCenter defaultCenter] removeObserver:self
251 name:AVPlayerItemDidPlayToEndTimeNotification
252 object:m_playerItem];
253 m_playerItem =
nullptr;
256 [m_player setRate:0.0];
257 [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_DURATION_KEY];
258 [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_KEY];
259 [m_player removeObserver:self forKeyPath:AVF_RATE_KEY];
260 [m_player replaceCurrentItemWithPlayerItem:nil];
266 AVPlayer *player = m_player;
268 dispatch_async(dispatch_get_main_queue(), ^{
273 m_playerLayer.player = nil;
274#if defined(Q_OS_IOS) || defined(Q_OS_VISIONOS) || defined(Q_OS_TVOS)
275 m_sessionActivation.reset();
279- (
void) prepareToPlayAsset:(AVURLAsset *)asset
280 withKeys:(NSArray *)requestedKeys
282 if (!m_platformPlayer)
285#if defined(Q_OS_IOS) || defined(Q_OS_VISIONOS) || defined(Q_OS_TVOS)
286 auto sessionActivation = QtMultimediaPrivate::QAVAudioSessionManager::instance()->activate({
288 AVAudioSessionCategoryOptionMixWithOthers,
293 for (NSString *thisKey in requestedKeys)
295 NSError *error = nil;
296 AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
298 qDebug() << Q_FUNC_INFO << [thisKey UTF8String] <<
" status: " << keyStatus;
300 if (keyStatus == AVKeyValueStatusFailed)
302 [self assetFailedToPrepareForPlayback:error];
309 qDebug() << Q_FUNC_INFO <<
"isPlayable: " << [asset isPlayable];
312 qWarning() <<
"Asset reported to be not playable. Playback of this asset may not be possible.";
323 m_playerItem = [AVPlayerItem playerItemWithAsset:asset];
325 qWarning() <<
"Failed to create player item";
327 NSString *localizedDescription = NSLocalizedString(@
"Item cannot be played", @
"Item cannot be played description");
328 NSString *localizedFailureReason = NSLocalizedString(@
"The assets tracks were loaded, but couldn't create player item.", @
"Item cannot be played failure reason");
329 NSDictionary *errorDict = @{
330 NSLocalizedDescriptionKey : localizedDescription,
331 NSLocalizedFailureReasonErrorKey : localizedFailureReason,
333 NSError *assetCannotBePlayedError = [NSError errorWithDomain:@
"StitchedStreamPlayer" code:0 userInfo:errorDict];
335 [self assetFailedToPrepareForPlayback:assetCannotBePlayedError];
340 [m_playerItem addObserver:self
341 forKeyPath:AVF_STATUS_KEY
342 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
343 context:AVFMediaPlayerObserverStatusObservationContext];
345 [m_playerItem addObserver:self
346 forKeyPath:@
"presentationSize"
347 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
348 context:AVFMediaPlayerObserverPresentationSizeContext];
350 [m_playerItem addObserver:self
351 forKeyPath:AVF_BUFFER_LIKELY_KEEP_UP_KEY
352 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
353 context:AVFMediaPlayerObserverBufferLikelyToKeepUpContext];
355 [m_playerItem addObserver:self
356 forKeyPath:AVF_TRACKS_KEY
357 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
358 context:AVFMediaPlayerObserverTracksContext];
362 [[NSNotificationCenter defaultCenter] addObserver:self
363 selector:@selector(playerItemDidReachEnd:)
364 name:AVPlayerItemDidPlayToEndTimeNotification
365 object:m_playerItem];
368 m_player = [AVPlayer playerWithPlayerItem:m_playerItem];
373 m_platformPlayer.withPlatformPlayer([&](AVFMediaPlayer *player) {
374 auto *audioOutput = player->m_audioOutput;
375 m_player.volume = (audioOutput ? audioOutput->volume : 1.);
376 m_player.muted = (audioOutput ? audioOutput->muted :
true);
377 player->updateAudioOutputDevice();
382 m_playerLayer.player = m_player;
387 [m_player addObserver:self
388 forKeyPath:AVF_CURRENT_ITEM_KEY
389 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
390 context:AVFMediaPlayerObserverCurrentItemObservationContext];
393 [m_player addObserver:self
394 forKeyPath:AVF_RATE_KEY
395 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
396 context:AVFMediaPlayerObserverRateObservationContext];
399 [m_player addObserver:self
400 forKeyPath:AVF_CURRENT_ITEM_DURATION_KEY
402 context:AVFMediaPlayerObserverCurrentItemDurationObservationContext];
403#if defined(Q_OS_IOS) || defined(Q_OS_VISIONOS) || defined(Q_OS_TVOS)
404 m_sessionActivation = std::move(sessionActivation);
408-(
void) assetFailedToPrepareForPlayback:(NSError *)error
410 QMediaPlayer::Error errorCode = QMediaPlayer::FormatError;
412 NSError *underlyingError = error.userInfo[NSUnderlyingErrorKey];
413 if (underlyingError && ![underlyingError.domain isEqualToString:AVFoundationErrorDomain])
414 errorCode = QMediaPlayer::ResourceError;
416 m_platformPlayer.invokeWithPlatformPlayer([errorCode](AVFMediaPlayer *platformPlayer) {
417 platformPlayer->processMediaLoadError(errorCode);
421 qDebug() << Q_FUNC_INFO;
422 qDebug() << [[error localizedDescription] UTF8String];
423 qDebug() << [[error localizedFailureReason] UTF8String];
424 qDebug() << [[error localizedRecoverySuggestion] UTF8String];
428- (
void)loadSubtitleOptionsForAsset:(AVAsset *)asset generation:(
int)generation
430 auto observer = retainObject(self);
431 [asset loadMediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible
432 completionHandler:[observer,
433 generation](AVMediaSelectionGroup *group,
435 std::shared_ptr<AVMediaSelectionGroup> retained = retainObject(group);
436 observer->m_platformPlayer.invokeWithPlatformPlayer(
437 [generation, retained](AVFMediaPlayer *platformPlayer) {
438 platformPlayer->subtitleOptionsLoaded(generation, retained.get());
443- (
void) playerItemDidReachEnd:(NSNotification *)notification
445 Q_UNUSED(notification);
447 m_platformPlayer.invokeWithPlatformPlayer([](AVFMediaPlayer *platformPlayer) {
448 platformPlayer->processEOS();
452- (
void) observeValueForKeyPath:(NSString*) path
454 change:(NSDictionary*)change
455 context:(
void*)context
458 if (context == AVFMediaPlayerObserverStatusObservationContext)
460 AVPlayerStatus status = (AVPlayerStatus)[[change objectForKey:NSKeyValueChangeNewKey] integerValue];
465 case AVPlayerStatusUnknown:
471 case AVPlayerStatusReadyToPlay:
477 m_platformPlayer.invokeWithPlatformPlayer([](AVFMediaPlayer *platformPlayer) {
478 platformPlayer->processLoadStateChange();
483 case AVPlayerStatusFailed:
485 AVPlayerItem *playerItem =
static_cast<AVPlayerItem*>(object);
486 [self assetFailedToPrepareForPlayback:playerItem.error];
488 m_platformPlayer.invokeWithPlatformPlayer([](AVFMediaPlayer *platformPlayer) {
489 platformPlayer->processLoadStateChange();
494 }
else if (context == AVFMediaPlayerObserverPresentationSizeContext) {
495 QSize size(m_playerItem.presentationSize.width, m_playerItem.presentationSize.height);
496 m_platformPlayer.invokeWithPlatformPlayer([size](AVFMediaPlayer *platformPlayer) {
497 platformPlayer->nativeSizeChanged(size);
499 }
else if (context == AVFMediaPlayerObserverBufferLikelyToKeepUpContext)
501 const bool isPlaybackLikelyToKeepUp = [m_playerItem isPlaybackLikelyToKeepUp];
502 if (isPlaybackLikelyToKeepUp != m_bufferIsLikelyToKeepUp) {
503 m_bufferIsLikelyToKeepUp = isPlaybackLikelyToKeepUp;
504 int bufferProgress = isPlaybackLikelyToKeepUp ? 100 : 0;
506 m_platformPlayer.invokeWithPlatformPlayer(
507 [bufferProgress](AVFMediaPlayer *platformPlayer) {
508 platformPlayer->processBufferStateChange(bufferProgress);
512 else if (context == AVFMediaPlayerObserverTracksContext)
514 m_platformPlayer.invokeWithPlatformPlayer([](AVFMediaPlayer *platformPlayer) {
515 platformPlayer->updateTracks();
519 else if (context == AVFMediaPlayerObserverRateObservationContext) {
525 else if (context == AVFMediaPlayerObserverCurrentItemObservationContext) {
526 AVPlayerItem *newPlayerItem = [change objectForKey:NSKeyValueChangeNewKey];
527 if (m_playerItem != newPlayerItem)
528 m_playerItem = newPlayerItem;
529 }
else if (context == AVFMediaPlayerObserverCurrentItemDurationObservationContext) {
530 const CMTime time = [m_playerItem duration];
531 const qint64 dur =
static_cast<qint64>(
float(time.value) /
float(time.timescale) * 1000.0f);
533 m_platformPlayer.invokeWithPlatformPlayer([dur](AVFMediaPlayer *platformPlayer) {
534 platformPlayer->processDurationChange(dur);
537 [super observeValueForKeyPath:path ofObject:object change:change context:context];
544 qDebug() << Q_FUNC_INFO;
546 m_platformPlayer.clear();
549- (
void)notifySeekComplete
551 m_platformPlayer.withPlatformPlayer([](AVFMediaPlayer *player) {
552 player->seekCompleted();
559 qDebug() << Q_FUNC_INFO;
563 m_platformPlayer.clear();
569 [m_mimeType release];
570 [m_playerLayer release];
573 self.videoTrack = nil;
577- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
579 Q_UNUSED(resourceLoader);
581 if (![loadingRequest.request.URL.scheme isEqualToString:@
"iodevice"])
584 auto result = m_platformPlayer.withPlatformPlayer([&](AVFMediaPlayer *platformPlayer) {
585 QIODevice *device = platformPlayer->mediaStream();
589 device->seek(loadingRequest.dataRequest.requestedOffset);
590 if (loadingRequest.contentInformationRequest) {
591 loadingRequest.contentInformationRequest.contentType = m_mimeType;
592 loadingRequest.contentInformationRequest.contentLength = device->size();
593 loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;
596 if (loadingRequest.dataRequest) {
597 NSInteger requestedLength = loadingRequest.dataRequest.requestedLength;
598 int maxBytes = qMin(32 * 1024,
int(requestedLength));
600 buffer.resize(maxBytes);
602 NSInteger submitted = 0;
603 while (submitted < requestedLength) {
604 qint64 len = device->read(buffer.data(), maxBytes);
608 [loadingRequest.dataRequest respondWithData:buffer.toNSData()];
613 [loadingRequest finishLoading];
619 return result.value_or(NO);
623AVFMediaPlayer::AVFMediaPlayer(QMediaPlayer *player)
625 QPlatformMediaPlayer(player),
626 m_mediaStream(
nullptr),
628 m_requestedPosition(-1),
632 m_observer = [[AVFMediaPlayerObserver alloc] initWithMediaPlayerSession:
this];
633 connect(&m_playbackTimer, &QTimer::timeout,
this, &AVFMediaPlayer::processPositionChange);
634 setVideoOutput(
new AVFVideoRendererControl(
this));
640 qDebug() << Q_FUNC_INFO;
645 [m_observer unloadMedia];
648 [m_observer clearSession];
649 [m_observer release];
650 [m_subtitleGroup release];
655 m_videoSink = sink ?
static_cast<
AVFVideoSink *>(sink->platformVideoSink()):
nullptr;
656 if (m_videoSink && m_nativeSize.isValid())
657 m_videoSink->setNativeSize(m_nativeSize);
664 qDebug() << Q_FUNC_INFO << output;
667 if (m_videoOutput == output)
672 m_videoOutput->setLayer(
nullptr);
675 m_videoOutput = output;
677 if (m_videoOutput && state() != QMediaPlayer::StoppedState)
678 m_videoOutput->setLayer([m_observer playerLayer]);
684 qDebug() << Q_FUNC_INFO;
686 AVAsset *currentAsset = [[m_observer playerItem] asset];
697 return m_mediaStream;
700static void setURL(AVFMediaPlayerObserver *observer,
const QUrl &url,
const QString &mimeType = QString())
702 QUrl resolvedUrl = url;
704 if (url.isLocalFile() && !QDir::isAbsolutePath(url.path()))
705 resolvedUrl = QUrl::fromLocalFile(QFileInfo(url.path()).absoluteFilePath());
706 NSURL *nsurl = resolvedUrl.toNSURL();
707 [observer setURL:nsurl mimeType:[NSString stringWithUTF8String:mimeType.toLatin1().constData()]];
714 qDebug() << Q_FUNC_INFO << content.request().url();
717 [m_observer unloadMedia];
720 m_resources = content;
723 m_requestedPosition = -1;
724 orientationChanged(QtVideo::Rotation::None,
false);
725 positionChanged(position());
726 if (m_duration != 0) {
730 if (!m_metaData.isEmpty()) {
734 resetBufferProgress();
735 resetSubtitleGroup();
736 for (size_t i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
738 nativeTracks[i].clear();
742 if (!m_mediaStream && content.isEmpty()) {
743 seekableChanged(
false);
744 audioAvailableChanged(
false);
745 videoAvailableChanged(
false);
747 mediaStatusChanged(QMediaPlayer::NoMedia);
748 stateChanged(QMediaPlayer::StoppedState);
753 mediaStatusChanged(QMediaPlayer::LoadingMedia);
758 if (m_mediaStream->size())
763 setURL(m_observer, m_resources);
766 stateChanged(QMediaPlayer::StoppedState);
771 AVPlayerItem *playerItem = [m_observer playerItem];
773 if (m_requestedPosition != -1)
774 return m_requestedPosition;
779 CMTime time = [playerItem currentTime];
780 return static_cast<quint64>(
float(time.value) /
float(time.timescale) * 1000.0f);
786 qDebug() << Q_FUNC_INFO;
794 qDebug() << Q_FUNC_INFO;
796 return m_bufferProgress/100.;
801 AVPlayerItem *playerItem = [m_observer playerItem];
806 if (state() == QMediaPlayer::StoppedState)
809 QMediaTimeRange timeRanges;
811 NSArray *ranges = [playerItem loadedTimeRanges];
812 for (NSValue *timeRange in ranges) {
813 CMTimeRange currentTimeRange = [timeRange CMTimeRangeValue];
814 qint64 startTime = qint64(
float(currentTimeRange.start.value) / currentTimeRange.start.timescale * 1000.0);
815 timeRanges.addInterval(startTime, startTime + qint64(
float(currentTimeRange.duration.value) / currentTimeRange.duration.timescale * 1000.0));
827 if (m_audioOutput == output)
830 m_audioOutput->q->disconnect(
this);
831 m_audioOutput = output;
839 setMuted(m_audioOutput ? m_audioOutput->muted :
true);
840 setVolume(m_audioOutput ? m_audioOutput->volume : 1.);
851 qDebug() << Q_FUNC_INFO << rate;
854 if (QtPrivate::fuzzyCompare(m_rate, rate))
859 AVPlayer *player = [m_observer player];
860 if (player && state() == QMediaPlayer::PlayingState)
861 [player setRate:m_rate];
863 playbackRateChanged(m_rate);
869 qDebug() << Q_FUNC_INFO << pos;
872 if (pos == position())
875 AVPlayerItem *playerItem = [m_observer playerItem];
877 m_requestedPosition = pos;
878 positionChanged(m_requestedPosition);
883 if (m_requestedPosition != -1) {
884 m_requestedPosition = -1;
885 positionChanged(position());
890 pos = qMax(qint64(0), pos);
892 pos = qMin(pos, duration());
893 m_requestedPosition = pos;
895 const CMTime newTime = CMTimeMake(pos, 1000);
896 AVFMediaPlayerObserver *observer = m_observer;
897 [playerItem seekToTime:newTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero
898 completionHandler:^(BOOL finished) {
900 [observer notifySeekComplete];
903 positionChanged(pos);
906 if (mediaStatus() == QMediaPlayer::EndOfMedia) {
907 QMediaPlayer::MediaStatus newMediaStatus = (state() == QMediaPlayer::PausedState)
908 ? QMediaPlayer::BufferedMedia
909 : QMediaPlayer::LoadedMedia;
910 mediaStatusChanged(newMediaStatus);
917 qDebug() << Q_FUNC_INFO <<
"currently: " << state();
920 if (mediaStatus() == QMediaPlayer::NoMedia || mediaStatus() == QMediaPlayer::InvalidMedia)
923 if (state() == QMediaPlayer::PlayingState)
926 if (state() != QMediaPlayer::PausedState)
929 if (m_videoOutput && m_videoSink)
930 m_videoOutput->setLayer([m_observer playerLayer]);
933 if (mediaStatus() == QMediaPlayer::EndOfMedia)
936 if (mediaStatus() == QMediaPlayer::LoadedMedia
937 || mediaStatus() == QMediaPlayer::BufferedMedia) {
939 [[m_observer player] setRate:m_rate];
944 stateChanged(QMediaPlayer::PlayingState);
945 m_playbackTimer.start(100);
951 qDebug() << Q_FUNC_INFO <<
"currently: " << state();
954 if (mediaStatus() == QMediaPlayer::NoMedia || mediaStatus() == QMediaPlayer::InvalidMedia)
957 if (state() == QMediaPlayer::PausedState)
960 stateChanged(QMediaPlayer::PausedState);
962 if (m_videoOutput && m_videoSink)
963 m_videoOutput->setLayer([m_observer playerLayer]);
965 [[m_observer player] pause];
968 if (mediaStatus() == QMediaPlayer::EndOfMedia)
971 positionChanged(position());
972 m_playbackTimer.stop();
978 qDebug() << Q_FUNC_INFO <<
"currently: " << state();
981 if (state() == QMediaPlayer::StoppedState && mediaStatus() != QMediaPlayer::EndOfMedia)
985 [[m_observer player] pause];
989 m_videoOutput->setLayer(
nullptr);
991 resetBufferProgress();
993 if (mediaStatus() == QMediaPlayer::BufferedMedia || mediaStatus() == QMediaPlayer::EndOfMedia)
994 mediaStatusChanged(QMediaPlayer::LoadedMedia);
996 stateChanged(QMediaPlayer::StoppedState);
997 m_playbackTimer.stop();
1003 qDebug() << Q_FUNC_INFO << volume;
1006 AVPlayer *player = [m_observer player];
1008 player.volume = volume;
1014 qDebug() << Q_FUNC_INFO << muted;
1017 AVPlayer *player = [m_observer player];
1019 player.muted = muted;
1025 AVPlayer *player = [m_observer player];
1029 if (!m_audioOutput || m_audioOutput->device.id().isEmpty()) {
1031 player.muted =
true;
1032 player.audioOutputDeviceUniqueID = nil;
1034 NSString *str = QString::fromUtf8(m_audioOutput->device.id()).toNSString();
1035 player.audioOutputDeviceUniqueID = str;
1043 positionChanged(duration());
1045 [[m_observer player] setRate:m_rate];
1051 qDebug() << Q_FUNC_INFO;
1053 positionChanged(position());
1056 m_videoOutput->setLayer(
nullptr);
1058 resetBufferProgress();
1060 stateChanged(QMediaPlayer::StoppedState);
1061 mediaStatusChanged(QMediaPlayer::EndOfMedia);
1066 AVPlayerStatus currentStatus = [[m_observer player] status];
1069 qDebug() << Q_FUNC_INFO << currentStatus <<
", " << mediaStatus() <<
", " << newState;
1072 if (mediaStatus() == QMediaPlayer::NoMedia)
1075 if (currentStatus == AVPlayerStatusReadyToPlay) {
1077 AVPlayerItem *playerItem = [m_observer playerItem];
1079 applyPitchCompensation(m_pitchCompensationEnabled);
1082 m_metaData = AVFMetaData::fromAsset(playerItem.asset);
1087 seekableChanged([[playerItem seekableTimeRanges] count] > 0);
1090 AVPlayerLayer *playerLayer = [m_observer playerLayer];
1091 if (m_observer.videoTrack && playerLayer) {
1092 if (!playerLayer.bounds.size.width || !playerLayer.bounds.size.height) {
1093 playerLayer.bounds = CGRectMake(0.0f, 0.0f,
1094 m_observer.videoTrack.assetTrack.naturalSize.width,
1095 m_observer.videoTrack.assetTrack.naturalSize.height);
1099 if (m_requestedPosition != -1)
1100 setPosition(m_requestedPosition);
1103 QMediaPlayer::MediaStatus newStatus = (newState != QMediaPlayer::StoppedState)
1104 ? QMediaPlayer::BufferedMedia
1105 : QMediaPlayer::LoadedMedia;
1107 if (newStatus != mediaStatus()) {
1108 if (newStatus == QMediaPlayer::BufferedMedia
1109 && mediaStatus() == QMediaPlayer::LoadingMedia) {
1111 mediaStatusChanged(QMediaPlayer::LoadedMedia);
1112 mediaStatusChanged(QMediaPlayer::BufferingMedia);
1113 }
else if (newStatus == QMediaPlayer::BufferedMedia
1114 && mediaStatus() == QMediaPlayer::LoadedMedia) {
1115 mediaStatusChanged(QMediaPlayer::BufferingMedia);
1117 mediaStatusChanged(newStatus);
1121 if (newState == QMediaPlayer::PlayingState && [m_observer player]) {
1123 [[m_observer player] setRate:m_rate];
1124 m_playbackTimer.start();
1137 stateChanged(QMediaPlayer::StoppedState);
1142 if (state() == QMediaPlayer::StoppedState)
1145 if (bufferProgress == m_bufferProgress)
1148 auto status = mediaStatus();
1150 if (!bufferProgress) {
1151 status = QMediaPlayer::StalledMedia;
1152 }
else if (status == QMediaPlayer::StalledMedia) {
1153 status = QMediaPlayer::BufferedMedia;
1155 if (state() == QMediaPlayer::PlayingState) {
1156 [[m_observer player] setRate:m_rate];
1157 m_playbackTimer.start();
1161 mediaStatusChanged(status);
1163 m_bufferProgress = bufferProgress;
1164 bufferProgressChanged(bufferProgress / 100.);
1169 if (duration == m_duration)
1172 m_duration = duration;
1173 durationChanged(duration);
1178 if (state() == QMediaPlayer::StoppedState)
1181 positionChanged(position());
1186 if (m_requestedPosition != -1) {
1187 m_requestedPosition = -1;
1188 positionChanged(position());
1191 setInvalidMediaWithError(errorCode, tr(
"Failed to load media"));
1196 m_requestedPosition = -1;
1207 if (!m_resources.isEmpty())
1208 suffix = QFileInfo(m_resources.path()).suffix();
1209 if (suffix.isEmpty() && m_mediaStream)
1210 suffix = QMimeDatabase().mimeTypeForData(m_mediaStream).preferredSuffix();
1211 const QString url = QStringLiteral(
"iodevice:///iodevice.") + suffix;
1212 setURL(m_observer, QUrl(url), suffix);
1217 resetStream(
nullptr);
1222 resetSubtitleGroup();
1223 bool firstLoad =
true;
1224 for (size_t i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
1225 if (tracks[i].count())
1228 nativeTracks[i].clear();
1230 bool hasAudio =
false;
1231 bool hasVideo =
false;
1232 AVPlayerItem *playerItem = [m_observer playerItem];
1235 NSArray *tracks = playerItem.tracks;
1236 for (AVPlayerItemTrack *track in tracks) {
1237 AVAssetTrack *assetTrack = track.assetTrack;
1239 std::optional<TrackType> qtTrack;
1240 if ([assetTrack.mediaType isEqualToString:AVMediaTypeAudio]) {
1241 qtTrack = TrackType::AudioStream;
1243 }
else if ([assetTrack.mediaType isEqualToString:AVMediaTypeVideo]) {
1244 qtTrack = TrackType::VideoStream;
1246 if (m_observer.videoTrack != track) {
1247 m_observer.videoTrack = track;
1248 bool isMirrored =
false;
1249 QtVideo::Rotation orientation = QtVideo::Rotation::None;
1250 videoOrientationForAssetTrack(assetTrack, orientation, isMirrored);
1251 orientationChanged(orientation, isMirrored);
1254 else if ([assetTrack.mediaType isEqualToString:AVMediaTypeSubtitle]) {
1255 qtTrack = TrackType::SubtitleStream;
1258 QMediaMetaData metaData = AVFMetaData::fromAssetTrack(assetTrack);
1259 this->tracks[*qtTrack].append(metaData);
1260 nativeTracks[*qtTrack].append(track);
1264 if (@available(macOS 26.0, iOS 26.0, *))
1265 [m_observer loadSubtitleOptionsForAsset:playerItem.asset
1266 generation:m_subtitleGroupGeneration];
1269 setActiveTrack(TrackType::SubtitleStream, -1);
1271 audioAvailableChanged(hasAudio);
1272 videoAvailableChanged(hasVideo);
1278 if (type == TrackType::SubtitleStream && m_subtitleGroup) {
1279 m_requestedSubtitleTrack = index;
1280 applySubtitleSelection();
1281 activeTracksChanged();
1285 const auto &t = nativeTracks[type];
1286 if (type == TrackType::SubtitleStream) {
1289 AVPlayerItem *playerItem = m_observer.m_playerItem;
1291 AVAsset *asset = playerItem.asset;
1294 [asset loadMediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible
1295 completionHandler:[=](AVMediaSelectionGroup *group,
1299 auto *options = group.options;
1302 AVMediaSelectionOption *option =
1303 index >= 0 && options.count ? options.firstObject : nil;
1304 if (!option && !group.allowsEmptySelection)
1306 [playerItem selectMediaOption:option inMediaSelectionGroup:group];
1310 for (
int i = 0; i < t.count(); ++i)
1311 t.at(i).enabled = (i == index);
1312 activeTracksChanged();
1317 if (generation != m_subtitleGroupGeneration || !group)
1320 bool hasGeneratedSubtitles =
false;
1321 for (AVMediaSelectionOption *option in group.options)
1322 hasGeneratedSubtitles |= isMachineGenerated(option);
1323 if (!hasGeneratedSubtitles)
1326 m_subtitleGroup = [group retain];
1328 QList<QMediaMetaData> subtitles;
1329 for (AVMediaSelectionOption *option in group.options)
1330 subtitles.append(subtitleMetaData(option));
1331 tracks[TrackType::SubtitleStream] = std::move(subtitles);
1333 applySubtitleSelection();
1335 activeTracksChanged();
1340 ++m_subtitleGroupGeneration;
1341 [m_subtitleGroup release];
1342 m_subtitleGroup = nil;
1347 AVPlayerItem *playerItem = [m_observer playerItem];
1348 if (!playerItem || !m_subtitleGroup)
1353 NSArray<AVMediaSelectionOption *> *options = m_subtitleGroup.options;
1354 const int index = m_requestedSubtitleTrack;
1355 AVMediaSelectionOption *option =
1356 index >= 0 && index <
int(options.count) ? options[index] : nil;
1357 if (!option && !m_subtitleGroup.allowsEmptySelection)
1359 [playerItem selectMediaOption:option inMediaSelectionGroup:m_subtitleGroup];
1364 if (type == TrackType::SubtitleStream && m_subtitleGroup) {
1365 AVPlayerItem *playerItem = [m_observer playerItem];
1368 AVMediaSelectionOption *selected = [playerItem.currentMediaSelection
1369 selectedMediaOptionInMediaSelectionGroup:m_subtitleGroup];
1370 const NSUInteger index = [m_subtitleGroup.options indexOfObject:selected];
1371 return index == NSNotFound ? -1 :
int(index);
1374 const auto &t = nativeTracks[type];
1375 for (
int i = 0; i < t.count(); ++i)
1376 if (t.at(i).enabled)
1383 if (type == TrackType::SubtitleStream && m_subtitleGroup)
1384 return tracks[type].count();
1385 return nativeTracks[type].count();
1390 const auto &t = tracks[type];
1391 if (trackNumber < 0 || trackNumber >= t.count())
1392 return QMediaMetaData();
1393 return t.at(trackNumber);
1398 if (m_mediaStream) {
1403 m_mediaStream = stream;
1405 if (m_mediaStream) {
1413 AVPlayerItem *playerItem = [m_observer playerItem];
1416 playerItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmSpectral;
1418 playerItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmVarispeed;
1424 if (m_bufferProgress != 0) {
1425 m_bufferProgress = 0;
1426 bufferProgressChanged(0);
1434 if (size.isEmpty() || size == m_nativeSize)
1437 m_nativeSize = size;
1440 m_videoSink->setNativeSize(size);
1443void AVFMediaPlayer::orientationChanged(QtVideo::Rotation rotation,
bool mirrored)
1448 m_videoOutput->setVideoRotation(rotation);
1453 QtVideo::Rotation &angle,
1456 angle = QtVideo::Rotation::None;
1459 CGAffineTransform transform = videoTrack.preferredTransform;
1460 if (CGAffineTransformIsIdentity(transform))
1464 qreal det = transform.a * transform.d - transform.b * transform.c;
1465 mirrored = (det < 0.0);
1469 qreal ra = mirrored ? -transform.a : transform.a;
1470 qreal rb = mirrored ? -transform.b : transform.b;
1472 qreal degrees = qRadiansToDegrees(qAtan2(rb, ra));
1476 if (QtPrivate::fuzzyCompare(degrees, qreal(90))
1477 || QtPrivate::fuzzyCompare(degrees, qreal(-270))) {
1478 angle = QtVideo::Rotation::Clockwise90;
1479 }
else if (QtPrivate::fuzzyCompare(degrees, qreal(-90))
1480 || QtPrivate::fuzzyCompare(degrees, qreal(270))) {
1481 angle = QtVideo::Rotation::Clockwise270;
1482 }
else if (QtPrivate::fuzzyCompare(degrees, qreal(180))
1483 || QtPrivate::fuzzyCompare(degrees, qreal(-180))) {
1484 angle = QtVideo::Rotation::Clockwise180;
1491 if (m_pitchCompensationEnabled == enabled)
1494 applyPitchCompensation(enabled);
1496 m_pitchCompensationEnabled = enabled;
1497 pitchCompensationChanged(enabled);
1502 return m_pitchCompensationEnabled;
1508 return QPlatformMediaPlayer::PitchCompensationAvailability::Available;
1511#include "moc_avfmediaplayer_p.cpp"
void setVideoMirrored(bool mirrored)
void setVideoSink(AVFVideoSink *sink)