6#include <avfvideosink_p.h>
7#include <avfmetadata_p.h>
10#include "private/qplatformaudiooutput_p.h"
12#include <QtCore/qdir.h>
13#include <QtCore/qfileinfo.h>
14#include <QtCore/qmimedatabase.h>
15#include <QtCore/qpointer.h>
16#include <QtCore/qmath.h>
17#include <QtCore/qmutex.h>
18#include <QtCore/qthread.h>
19#include <QtCore/private/qexpected_p.h>
23#import <AVFoundation/AVFoundation.h>
28static NSString*
const AVF_TRACKS_KEY = @
"tracks";
49@interface AVFMediaPlayerObserver : NSObject<AVAssetResourceLoaderDelegate>
51@property (readonly, getter=player) AVPlayer* m_player;
52@property (readonly, getter=playerItem) AVPlayerItem* m_playerItem;
53@property (readonly, getter=playerLayer) AVPlayerLayer* m_playerLayer;
54@property (retain) AVPlayerItemTrack *videoTrack;
56- (AVFMediaPlayerObserver *) initWithMediaPlayerSession:(AVFMediaPlayer *)session;
57- (
void) setURL:(NSURL *)url mimeType:(NSString *)mimeType;
59- (
void) prepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys;
60- (
void) assetFailedToPrepareForPlayback:(NSError *)error;
61- (
void) playerItemDidReachEnd:(NSNotification *)notification;
62- (
void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
63 change:(NSDictionary *)change context:(
void *)context;
65- (
void) notifySeekComplete;
67- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest;
72static unsigned sessionActivationCount;
73static QMutex sessionMutex;
78struct GuardedPlatformPlayer
83 explicit operator
bool()
const
85 std::lock_guard guard(mutex);
89 struct not_a_platform_player_t
93 template <
typename Functor>
94 auto withPlatformPlayer(Functor &&f)
95 -> q23::expected<std::invoke_result_t<Functor, AVFMediaPlayer *>,
96 not_a_platform_player_t>
98 std::unique_lock guard(mutex);
100 return q23::unexpected{ not_a_platform_player_t{} };
101 if constexpr (std::is_void_v<std::invoke_result_t<Functor, AVFMediaPlayer *>>) {
109 template <
typename Functor>
110 void invokeWithPlatformPlayer(Functor f)
112 std::unique_lock guard(mutex);
116 if (player->thread()->isCurrentThread()) {
120 QMetaObject::invokeMethod(player, [f = std::move(f), player = player]() {
128 std::lock_guard<QMutex> guard(mutex);
134@implementation AVFMediaPlayerObserver {
136 GuardedPlatformPlayer m_platformPlayer;
138 AVPlayerItem *m_playerItem;
139 AVPlayerLayer *m_playerLayer;
141 BOOL m_bufferIsLikelyToKeepUp;
143 NSString *m_mimeType;
149@synthesize m_player, m_playerItem, m_playerLayer;
152- (
void)setSessionActive:(BOOL)active
154 const QMutexLocker lock(&sessionMutex);
160 if (!sessionActivationCount)
161 [AVAudioSession.sharedInstance setActive:YES error:nil];
162 ++sessionActivationCount;
165 if (!sessionActivationCount || !m_activated) {
166 qWarning(
"Unbalanced audio session deactivation, ignoring.");
169 --sessionActivationCount;
171 if (!sessionActivationCount)
172 [AVAudioSession.sharedInstance setActive:NO error:nil];
177- (AVFMediaPlayerObserver *) initWithMediaPlayerSession:(AVFMediaPlayer *)session
179 if (!(self = [super init]))
181 m_platformPlayer.player = session;
182 m_bufferIsLikelyToKeepUp = FALSE;
184 m_playerLayer = [AVPlayerLayer playerLayerWithPlayer:nil];
185 [m_playerLayer retain];
186 m_playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
187 m_playerLayer.anchorPoint = CGPointMake(0.0f, 0.0f);
191- (
void) setURL:(NSURL *)url mimeType:(NSString *)mimeType
193 [m_mimeType release];
194 m_mimeType = [mimeType retain];
207 BOOL isAccessing = [m_URL startAccessingSecurityScopedResource];
209 __block AVURLAsset *asset = [[AVURLAsset URLAssetWithURL:m_URL options:nil] retain];
210 [asset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()];
212 __block NSArray *requestedKeys = [[NSArray arrayWithObjects:AVF_TRACKS_KEY, AVF_PLAYABLE_KEY, nil] retain];
214 __block AVFMediaPlayerObserver *blockSelf = [self retain];
217 [asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:
219 dispatch_async( dispatch_get_main_queue(),
223 [m_URL stopAccessingSecurityScopedResource];
225 [blockSelf prepareToPlayAsset:asset withKeys:requestedKeys];
227 [requestedKeys release];
237 [m_playerItem removeObserver:self forKeyPath:@
"presentationSize"];
238 [m_playerItem removeObserver:self forKeyPath:AVF_STATUS_KEY];
239 [m_playerItem removeObserver:self forKeyPath:AVF_BUFFER_LIKELY_KEEP_UP_KEY];
240 [m_playerItem removeObserver:self forKeyPath:AVF_TRACKS_KEY];
242 [[NSNotificationCenter defaultCenter] removeObserver:self
243 name:AVPlayerItemDidPlayToEndTimeNotification
244 object:m_playerItem];
245 m_playerItem =
nullptr;
248 [m_player setRate:0.0];
249 [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_DURATION_KEY];
250 [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_KEY];
251 [m_player removeObserver:self forKeyPath:AVF_RATE_KEY];
252 [m_player replaceCurrentItemWithPlayerItem:nil];
258 AVPlayer *player = m_player;
260 dispatch_async(dispatch_get_main_queue(), ^{
265 m_playerLayer.player = nil;
267 [self setSessionActive:NO];
271- (
void) prepareToPlayAsset:(AVURLAsset *)asset
272 withKeys:(NSArray *)requestedKeys
274 if (!m_platformPlayer)
278 for (NSString *thisKey in requestedKeys)
280 NSError *error = nil;
281 AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
283 qDebug() << Q_FUNC_INFO << [thisKey UTF8String] <<
" status: " << keyStatus;
285 if (keyStatus == AVKeyValueStatusFailed)
287 [self assetFailedToPrepareForPlayback:error];
294 qDebug() << Q_FUNC_INFO <<
"isPlayable: " << [asset isPlayable];
297 qWarning() <<
"Asset reported to be not playable. Playback of this asset may not be possible.";
308 m_playerItem = [AVPlayerItem playerItemWithAsset:asset];
310 qWarning() <<
"Failed to create player item";
312 NSString *localizedDescription = NSLocalizedString(@
"Item cannot be played", @
"Item cannot be played description");
313 NSString *localizedFailureReason = NSLocalizedString(@
"The assets tracks were loaded, but couldn't create player item.", @
"Item cannot be played failure reason");
314 NSDictionary *errorDict = [NSDictionary dictionaryWithObjectsAndKeys:
315 localizedDescription, NSLocalizedDescriptionKey,
316 localizedFailureReason, NSLocalizedFailureReasonErrorKey,
318 NSError *assetCannotBePlayedError = [NSError errorWithDomain:@
"StitchedStreamPlayer" code:0 userInfo:errorDict];
320 [self assetFailedToPrepareForPlayback:assetCannotBePlayedError];
325 [m_playerItem addObserver:self
326 forKeyPath:AVF_STATUS_KEY
327 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
328 context:AVFMediaPlayerObserverStatusObservationContext];
330 [m_playerItem addObserver:self
331 forKeyPath:@
"presentationSize"
332 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
333 context:AVFMediaPlayerObserverPresentationSizeContext];
335 [m_playerItem addObserver:self
336 forKeyPath:AVF_BUFFER_LIKELY_KEEP_UP_KEY
337 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
338 context:AVFMediaPlayerObserverBufferLikelyToKeepUpContext];
340 [m_playerItem addObserver:self
341 forKeyPath:AVF_TRACKS_KEY
342 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
343 context:AVFMediaPlayerObserverTracksContext];
347 [[NSNotificationCenter defaultCenter] addObserver:self
348 selector:@selector(playerItemDidReachEnd:)
349 name:AVPlayerItemDidPlayToEndTimeNotification
350 object:m_playerItem];
353 m_player = [AVPlayer playerWithPlayerItem:m_playerItem];
358 m_platformPlayer.withPlatformPlayer([&](AVFMediaPlayer *player) {
359 auto *audioOutput = player->m_audioOutput;
360 m_player.volume = (audioOutput ? audioOutput->volume : 1.);
361 m_player.muted = (audioOutput ? audioOutput->muted :
true);
362 player->updateAudioOutputDevice();
367 m_playerLayer.player = m_player;
372 [m_player addObserver:self
373 forKeyPath:AVF_CURRENT_ITEM_KEY
374 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
375 context:AVFMediaPlayerObserverCurrentItemObservationContext];
378 [m_player addObserver:self
379 forKeyPath:AVF_RATE_KEY
380 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
381 context:AVFMediaPlayerObserverRateObservationContext];
384 [m_player addObserver:self
385 forKeyPath:AVF_CURRENT_ITEM_DURATION_KEY
387 context:AVFMediaPlayerObserverCurrentItemDurationObservationContext];
389 [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
390 [self setSessionActive:YES];
394-(
void) assetFailedToPrepareForPlayback:(NSError *)error
396 QMediaPlayer::Error errorCode = QMediaPlayer::FormatError;
398 NSError *underlyingError = error.userInfo[NSUnderlyingErrorKey];
399 if (underlyingError && ![underlyingError.domain isEqualToString:AVFoundationErrorDomain])
400 errorCode = QMediaPlayer::ResourceError;
402 m_platformPlayer.invokeWithPlatformPlayer([errorCode](AVFMediaPlayer *platformPlayer) {
403 platformPlayer->processMediaLoadError(errorCode);
407 qDebug() << Q_FUNC_INFO;
408 qDebug() << [[error localizedDescription] UTF8String];
409 qDebug() << [[error localizedFailureReason] UTF8String];
410 qDebug() << [[error localizedRecoverySuggestion] UTF8String];
414- (
void) playerItemDidReachEnd:(NSNotification *)notification
416 Q_UNUSED(notification);
418 m_platformPlayer.invokeWithPlatformPlayer([](AVFMediaPlayer *platformPlayer) {
419 platformPlayer->processEOS();
423- (
void) observeValueForKeyPath:(NSString*) path
425 change:(NSDictionary*)change
426 context:(
void*)context
429 if (context == AVFMediaPlayerObserverStatusObservationContext)
431 AVPlayerStatus status = (AVPlayerStatus)[[change objectForKey:NSKeyValueChangeNewKey] integerValue];
436 case AVPlayerStatusUnknown:
442 case AVPlayerStatusReadyToPlay:
448 m_platformPlayer.invokeWithPlatformPlayer([](AVFMediaPlayer *platformPlayer) {
449 platformPlayer->processLoadStateChange();
454 case AVPlayerStatusFailed:
456 AVPlayerItem *playerItem =
static_cast<AVPlayerItem*>(object);
457 [self assetFailedToPrepareForPlayback:playerItem.error];
459 m_platformPlayer.invokeWithPlatformPlayer([](AVFMediaPlayer *platformPlayer) {
460 platformPlayer->processLoadStateChange();
465 }
else if (context == AVFMediaPlayerObserverPresentationSizeContext) {
466 QSize size(m_playerItem.presentationSize.width, m_playerItem.presentationSize.height);
467 m_platformPlayer.invokeWithPlatformPlayer([size](AVFMediaPlayer *platformPlayer) {
468 platformPlayer->nativeSizeChanged(size);
470 }
else if (context == AVFMediaPlayerObserverBufferLikelyToKeepUpContext)
472 const bool isPlaybackLikelyToKeepUp = [m_playerItem isPlaybackLikelyToKeepUp];
473 if (isPlaybackLikelyToKeepUp != m_bufferIsLikelyToKeepUp) {
474 m_bufferIsLikelyToKeepUp = isPlaybackLikelyToKeepUp;
475 int bufferProgress = isPlaybackLikelyToKeepUp ? 100 : 0;
477 m_platformPlayer.invokeWithPlatformPlayer(
478 [bufferProgress](AVFMediaPlayer *platformPlayer) {
479 platformPlayer->processBufferStateChange(bufferProgress);
483 else if (context == AVFMediaPlayerObserverTracksContext)
485 m_platformPlayer.invokeWithPlatformPlayer([](AVFMediaPlayer *platformPlayer) {
486 platformPlayer->updateTracks();
490 else if (context == AVFMediaPlayerObserverRateObservationContext) {
496 else if (context == AVFMediaPlayerObserverCurrentItemObservationContext) {
497 AVPlayerItem *newPlayerItem = [change objectForKey:NSKeyValueChangeNewKey];
498 if (m_playerItem != newPlayerItem)
499 m_playerItem = newPlayerItem;
500 }
else if (context == AVFMediaPlayerObserverCurrentItemDurationObservationContext) {
501 const CMTime time = [m_playerItem duration];
502 const qint64 dur =
static_cast<qint64>(
float(time.value) /
float(time.timescale) * 1000.0f);
504 m_platformPlayer.invokeWithPlatformPlayer([dur](AVFMediaPlayer *platformPlayer) {
505 platformPlayer->processDurationChange(dur);
508 [super observeValueForKeyPath:path ofObject:object change:change context:context];
515 qDebug() << Q_FUNC_INFO;
517 m_platformPlayer.clear();
520- (
void)notifySeekComplete
522 m_platformPlayer.withPlatformPlayer([](AVFMediaPlayer *player) {
523 player->seekCompleted();
530 qDebug() << Q_FUNC_INFO;
534 m_platformPlayer.clear();
540 [m_mimeType release];
541 [m_playerLayer release];
544 self.videoTrack = nil;
548- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
550 Q_UNUSED(resourceLoader);
552 if (![loadingRequest.request.URL.scheme isEqualToString:@
"iodevice"])
555 auto result = m_platformPlayer.withPlatformPlayer([&](AVFMediaPlayer *platformPlayer) {
556 QIODevice *device = platformPlayer->mediaStream();
560 device->seek(loadingRequest.dataRequest.requestedOffset);
561 if (loadingRequest.contentInformationRequest) {
562 loadingRequest.contentInformationRequest.contentType = m_mimeType;
563 loadingRequest.contentInformationRequest.contentLength = device->size();
564 loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;
567 if (loadingRequest.dataRequest) {
568 NSInteger requestedLength = loadingRequest.dataRequest.requestedLength;
569 int maxBytes = qMin(32 * 1064,
int(requestedLength));
571 buffer.resize(maxBytes);
573 NSInteger submitted = 0;
574 while (submitted < requestedLength) {
575 qint64 len = device->read(buffer.data(), maxBytes);
579 [loadingRequest.dataRequest respondWithData:[NSData dataWithBytes:buffer.constData()
585 [loadingRequest finishLoading];
591 return result.value_or(NO);
595AVFMediaPlayer::AVFMediaPlayer(QMediaPlayer *player)
597 QPlatformMediaPlayer(player),
598 m_mediaStream(
nullptr),
600 m_requestedPosition(-1),
604 m_observer = [[AVFMediaPlayerObserver alloc] initWithMediaPlayerSession:
this];
605 connect(&m_playbackTimer, &QTimer::timeout,
this, &AVFMediaPlayer::processPositionChange);
606 setVideoOutput(
new AVFVideoRendererControl(
this));
612 qDebug() << Q_FUNC_INFO;
617 [m_observer unloadMedia];
620 [m_observer clearSession];
621 [m_observer release];
626 m_videoSink = sink ?
static_cast<
AVFVideoSink *>(sink->platformVideoSink()):
nullptr;
633 qDebug() << Q_FUNC_INFO << output;
636 if (m_videoOutput == output)
641 m_videoOutput->setLayer(
nullptr);
644 m_videoOutput = output;
646 if (m_videoOutput && state() != QMediaPlayer::StoppedState)
647 m_videoOutput->setLayer([m_observer playerLayer]);
653 qDebug() << Q_FUNC_INFO;
655 AVAsset *currentAsset = [[m_observer playerItem] asset];
666 return m_mediaStream;
669static void setURL(AVFMediaPlayerObserver *observer,
const QUrl &url,
const QString &mimeType = QString())
671 QUrl resolvedUrl = url;
673 if (url.isLocalFile() && !QDir::isAbsolutePath(url.path()))
674 resolvedUrl = QUrl::fromLocalFile(QFileInfo(url.path()).absoluteFilePath());
675 NSURL *nsurl = resolvedUrl.toNSURL();
676 [observer setURL:nsurl mimeType:[NSString stringWithUTF8String:mimeType.toLatin1().constData()]];
683 qDebug() << Q_FUNC_INFO << content.request().url();
686 [m_observer unloadMedia];
688 m_resources = content;
691 m_requestedPosition = -1;
692 orientationChanged(QtVideo::Rotation::None,
false);
693 positionChanged(position());
694 if (m_duration != 0) {
698 if (!m_metaData.isEmpty()) {
702 resetBufferProgress();
703 for (
int i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
705 nativeTracks[i].clear();
709 if (!m_mediaStream && content.isEmpty()) {
710 seekableChanged(
false);
711 audioAvailableChanged(
false);
712 videoAvailableChanged(
false);
714 mediaStatusChanged(QMediaPlayer::NoMedia);
715 stateChanged(QMediaPlayer::StoppedState);
720 mediaStatusChanged(QMediaPlayer::LoadingMedia);
725 if (m_mediaStream->size())
730 setURL(m_observer, m_resources);
733 stateChanged(QMediaPlayer::StoppedState);
738 AVPlayerItem *playerItem = [m_observer playerItem];
740 if (m_requestedPosition != -1)
741 return m_requestedPosition;
746 CMTime time = [playerItem currentTime];
747 return static_cast<quint64>(
float(time.value) /
float(time.timescale) * 1000.0f);
753 qDebug() << Q_FUNC_INFO;
761 qDebug() << Q_FUNC_INFO;
763 return m_bufferProgress/100.;
768 AVPlayerItem *playerItem = [m_observer playerItem];
773 if (state() == QMediaPlayer::StoppedState)
776 QMediaTimeRange timeRanges;
778 NSArray *ranges = [playerItem loadedTimeRanges];
779 for (NSValue *timeRange in ranges) {
780 CMTimeRange currentTimeRange = [timeRange CMTimeRangeValue];
781 qint64 startTime = qint64(
float(currentTimeRange.start.value) / currentTimeRange.start.timescale * 1000.0);
782 timeRanges.addInterval(startTime, startTime + qint64(
float(currentTimeRange.duration.value) / currentTimeRange.duration.timescale * 1000.0));
794 if (m_audioOutput == output)
797 m_audioOutput->q->disconnect(
this);
798 m_audioOutput = output;
806 setMuted(m_audioOutput ? m_audioOutput->muted :
true);
807 setVolume(m_audioOutput ? m_audioOutput->volume : 1.);
818 qDebug() << Q_FUNC_INFO << rate;
821 if (QtPrivate::fuzzyCompare(m_rate, rate))
826 AVPlayer *player = [m_observer player];
827 if (player && state() == QMediaPlayer::PlayingState)
828 [player setRate:m_rate];
830 playbackRateChanged(m_rate);
836 qDebug() << Q_FUNC_INFO << pos;
839 if (pos == position())
842 AVPlayerItem *playerItem = [m_observer playerItem];
844 m_requestedPosition = pos;
845 positionChanged(m_requestedPosition);
850 if (m_requestedPosition != -1) {
851 m_requestedPosition = -1;
852 positionChanged(position());
857 pos = qMax(qint64(0), pos);
859 pos = qMin(pos, duration());
860 m_requestedPosition = pos;
862 CMTime newTime = [playerItem currentTime];
863 newTime.value = (pos / 1000.0f) * newTime.timescale;
864 AVFMediaPlayerObserver *observer = m_observer;
865 [playerItem seekToTime:newTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero
866 completionHandler:^(BOOL finished) {
868 [observer notifySeekComplete];
871 positionChanged(pos);
874 if (mediaStatus() == QMediaPlayer::EndOfMedia) {
875 QMediaPlayer::MediaStatus newMediaStatus = (state() == QMediaPlayer::PausedState)
876 ? QMediaPlayer::BufferedMedia
877 : QMediaPlayer::LoadedMedia;
878 mediaStatusChanged(newMediaStatus);
885 qDebug() << Q_FUNC_INFO <<
"currently: " << state();
888 if (mediaStatus() == QMediaPlayer::NoMedia || mediaStatus() == QMediaPlayer::InvalidMedia)
891 if (state() == QMediaPlayer::PlayingState)
894 if (state() != QMediaPlayer::PausedState)
897 if (m_videoOutput && m_videoSink)
898 m_videoOutput->setLayer([m_observer playerLayer]);
901 if (mediaStatus() == QMediaPlayer::EndOfMedia)
904 if (mediaStatus() == QMediaPlayer::LoadedMedia
905 || mediaStatus() == QMediaPlayer::BufferedMedia) {
907 [[m_observer player] setRate:m_rate];
912 stateChanged(QMediaPlayer::PlayingState);
913 m_playbackTimer.start(100);
919 qDebug() << Q_FUNC_INFO <<
"currently: " << state();
922 if (mediaStatus() == QMediaPlayer::NoMedia || mediaStatus() == QMediaPlayer::InvalidMedia)
925 if (state() == QMediaPlayer::PausedState)
928 stateChanged(QMediaPlayer::PausedState);
930 if (m_videoOutput && m_videoSink)
931 m_videoOutput->setLayer([m_observer playerLayer]);
933 [[m_observer player] pause];
936 if (mediaStatus() == QMediaPlayer::EndOfMedia)
939 positionChanged(position());
940 m_playbackTimer.stop();
946 qDebug() << Q_FUNC_INFO <<
"currently: " << state();
949 if (state() == QMediaPlayer::StoppedState && mediaStatus() != QMediaPlayer::EndOfMedia)
953 [[m_observer player] pause];
957 m_videoOutput->setLayer(
nullptr);
959 resetBufferProgress();
961 if (mediaStatus() == QMediaPlayer::BufferedMedia || mediaStatus() == QMediaPlayer::EndOfMedia)
962 mediaStatusChanged(QMediaPlayer::LoadedMedia);
964 stateChanged(QMediaPlayer::StoppedState);
965 m_playbackTimer.stop();
971 qDebug() << Q_FUNC_INFO << volume;
974 AVPlayer *player = [m_observer player];
976 player.volume = volume;
982 qDebug() << Q_FUNC_INFO << muted;
985 AVPlayer *player = [m_observer player];
987 player.muted = muted;
993 AVPlayer *player = [m_observer player];
997 if (!m_audioOutput || m_audioOutput->device.id().isEmpty()) {
1000 player.audioOutputDeviceUniqueID = nil;
1002 NSString *str = QString::fromUtf8(m_audioOutput->device.id()).toNSString();
1003 player.audioOutputDeviceUniqueID = str;
1011 positionChanged(duration());
1013 [[m_observer player] setRate:m_rate];
1019 qDebug() << Q_FUNC_INFO;
1021 positionChanged(position());
1024 m_videoOutput->setLayer(
nullptr);
1026 resetBufferProgress();
1028 stateChanged(QMediaPlayer::StoppedState);
1029 mediaStatusChanged(QMediaPlayer::EndOfMedia);
1034 AVPlayerStatus currentStatus = [[m_observer player] status];
1037 qDebug() << Q_FUNC_INFO << currentStatus <<
", " << mediaStatus() <<
", " << newState;
1040 if (mediaStatus() == QMediaPlayer::NoMedia)
1043 if (currentStatus == AVPlayerStatusReadyToPlay) {
1045 AVPlayerItem *playerItem = [m_observer playerItem];
1047 applyPitchCompensation(m_pitchCompensationEnabled);
1050 m_metaData = AVFMetaData::fromAsset(playerItem.asset);
1055 seekableChanged([[playerItem seekableTimeRanges] count] > 0);
1058 AVPlayerLayer *playerLayer = [m_observer playerLayer];
1059 if (m_observer.videoTrack && playerLayer) {
1060 if (!playerLayer.bounds.size.width || !playerLayer.bounds.size.height) {
1061 playerLayer.bounds = CGRectMake(0.0f, 0.0f,
1062 m_observer.videoTrack.assetTrack.naturalSize.width,
1063 m_observer.videoTrack.assetTrack.naturalSize.height);
1067 if (m_requestedPosition != -1)
1068 setPosition(m_requestedPosition);
1071 QMediaPlayer::MediaStatus newStatus = (newState != QMediaPlayer::StoppedState)
1072 ? QMediaPlayer::BufferedMedia
1073 : QMediaPlayer::LoadedMedia;
1075 if (newStatus != mediaStatus()) {
1076 if (newStatus == QMediaPlayer::BufferedMedia
1077 && mediaStatus() == QMediaPlayer::LoadingMedia) {
1079 mediaStatusChanged(QMediaPlayer::LoadedMedia);
1080 mediaStatusChanged(QMediaPlayer::BufferingMedia);
1081 }
else if (newStatus == QMediaPlayer::BufferedMedia
1082 && mediaStatus() == QMediaPlayer::LoadedMedia) {
1083 mediaStatusChanged(QMediaPlayer::BufferingMedia);
1085 mediaStatusChanged(newStatus);
1089 if (newState == QMediaPlayer::PlayingState && [m_observer player]) {
1091 [[m_observer player] setRate:m_rate];
1092 m_playbackTimer.start();
1105 stateChanged(QMediaPlayer::StoppedState);
1110 if (state() == QMediaPlayer::StoppedState)
1113 if (bufferProgress == m_bufferProgress)
1116 auto status = mediaStatus();
1118 if (!bufferProgress) {
1119 status = QMediaPlayer::StalledMedia;
1120 }
else if (status == QMediaPlayer::StalledMedia) {
1121 status = QMediaPlayer::BufferedMedia;
1123 if (state() == QMediaPlayer::PlayingState) {
1124 [[m_observer player] setRate:m_rate];
1125 m_playbackTimer.start();
1129 mediaStatusChanged(status);
1131 m_bufferProgress = bufferProgress;
1132 bufferProgressChanged(bufferProgress / 100.);
1137 if (duration == m_duration)
1140 m_duration = duration;
1141 durationChanged(duration);
1146 if (state() == QMediaPlayer::StoppedState)
1149 positionChanged(position());
1154 if (m_requestedPosition != -1) {
1155 m_requestedPosition = -1;
1156 positionChanged(position());
1159 setInvalidMediaWithError(errorCode, tr(
"Failed to load media"));
1164 m_requestedPosition = -1;
1175 if (!m_resources.isEmpty())
1176 suffix = QFileInfo(m_resources.path()).suffix();
1177 if (suffix.isEmpty() && m_mediaStream)
1178 suffix = QMimeDatabase().mimeTypeForData(m_mediaStream).preferredSuffix();
1179 const QString url = QStringLiteral(
"iodevice:///iodevice.") + suffix;
1180 setURL(m_observer, QUrl(url), suffix);
1185 resetStream(
nullptr);
1190 bool firstLoad =
true;
1191 for (
int i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
1192 if (tracks[i].count())
1195 nativeTracks[i].clear();
1197 bool hasAudio =
false;
1198 bool hasVideo =
false;
1199 AVPlayerItem *playerItem = [m_observer playerItem];
1202 NSArray *tracks = playerItem.tracks;
1203 for (AVPlayerItemTrack *track in tracks) {
1204 AVAssetTrack *assetTrack = track.assetTrack;
1207 if ([assetTrack.mediaType isEqualToString:AVMediaTypeAudio]) {
1208 qtTrack = QPlatformMediaPlayer::AudioStream;
1210 }
else if ([assetTrack.mediaType isEqualToString:AVMediaTypeVideo]) {
1211 qtTrack = QPlatformMediaPlayer::VideoStream;
1213 if (m_observer.videoTrack != track) {
1214 m_observer.videoTrack = track;
1215 bool isMirrored =
false;
1216 QtVideo::Rotation orientation = QtVideo::Rotation::None;
1217 videoOrientationForAssetTrack(assetTrack, orientation, isMirrored);
1218 orientationChanged(orientation, isMirrored);
1221 else if ([assetTrack.mediaType isEqualToString:AVMediaTypeSubtitle]) {
1222 qtTrack = QPlatformMediaPlayer::SubtitleStream;
1224 if (qtTrack != -1) {
1225 QMediaMetaData metaData = AVFMetaData::fromAssetTrack(assetTrack);
1226 this->tracks[qtTrack].append(metaData);
1227 nativeTracks[qtTrack].append(track);
1233 setActiveTrack(SubtitleStream, -1);
1235 audioAvailableChanged(hasAudio);
1236 videoAvailableChanged(hasVideo);
1242 const auto &t = nativeTracks[type];
1243 if (type == QPlatformMediaPlayer::SubtitleStream) {
1246 AVPlayerItem *playerItem = m_observer.m_playerItem;
1248 AVAsset *asset = playerItem.asset;
1251#if defined(Q_OS_VISIONOS)
1252 [asset loadMediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible
1253 completionHandler:[=](AVMediaSelectionGroup *group, NSError *error) {
1257 auto *options = group.options;
1259 [playerItem selectMediaOption:options.firstObject inMediaSelectionGroup:group];
1262 AVMediaSelectionGroup *group = [asset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
1265 auto *options = group.options;
1267 [playerItem selectMediaOption:options.firstObject inMediaSelectionGroup:group];
1271 for (
int i = 0; i < t.count(); ++i)
1272 t.at(i).enabled = (i == index);
1273 activeTracksChanged();
1278 const auto &t = nativeTracks[type];
1279 for (
int i = 0; i < t.count(); ++i)
1280 if (t.at(i).enabled)
1287 return nativeTracks[type].count();
1290QMediaMetaData
AVFMediaPlayer::trackMetaData(QPlatformMediaPlayer::TrackType type,
int trackNumber)
1292 const auto &t = tracks[type];
1293 if (trackNumber < 0 || trackNumber >= t.count())
1294 return QMediaMetaData();
1295 return t.at(trackNumber);
1300 if (m_mediaStream) {
1305 m_mediaStream = stream;
1307 if (m_mediaStream) {
1315 AVPlayerItem *playerItem = [m_observer playerItem];
1318 playerItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmSpectral;
1320 playerItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmVarispeed;
1326 if (m_bufferProgress != 0) {
1327 m_bufferProgress = 0;
1328 bufferProgressChanged(0);
1336 m_videoSink->setNativeSize(size);
1339void AVFMediaPlayer::orientationChanged(QtVideo::Rotation rotation,
bool mirrored)
1344 m_videoOutput->setVideoRotation(rotation);
1349 QtVideo::Rotation &angle,
1352 angle = QtVideo::Rotation::None;
1355 CGAffineTransform transform = videoTrack.preferredTransform;
1356 if (CGAffineTransformIsIdentity(transform))
1360 qreal det = transform.a * transform.d - transform.b * transform.c;
1361 mirrored = (det < 0.0);
1365 qreal ra = mirrored ? -transform.a : transform.a;
1366 qreal rb = mirrored ? -transform.b : transform.b;
1368 qreal degrees = qRadiansToDegrees(qAtan2(rb, ra));
1372 if (QtPrivate::fuzzyCompare(degrees, qreal(90))
1373 || QtPrivate::fuzzyCompare(degrees, qreal(-270))) {
1374 angle = QtVideo::Rotation::Clockwise90;
1375 }
else if (QtPrivate::fuzzyCompare(degrees, qreal(-90))
1376 || QtPrivate::fuzzyCompare(degrees, qreal(270))) {
1377 angle = QtVideo::Rotation::Clockwise270;
1378 }
else if (QtPrivate::fuzzyCompare(degrees, qreal(180))
1379 || QtPrivate::fuzzyCompare(degrees, qreal(-180))) {
1380 angle = QtVideo::Rotation::Clockwise180;
1387 if (m_pitchCompensationEnabled == enabled)
1390 applyPitchCompensation(enabled);
1392 m_pitchCompensationEnabled = enabled;
1393 pitchCompensationChanged(enabled);
1398 return m_pitchCompensationEnabled;
1404 return QPlatformMediaPlayer::PitchCompensationAvailability::Available;
1407#include "moc_avfmediaplayer_p.cpp"
void setVideoMirrored(bool mirrored)
void setVideoSink(AVFVideoSink *sink)