6#include <avfvideosink_p.h>
7#include <avfmetadata_p.h>
10#include "private/qplatformaudiooutput_p.h"
14#include <QtCore/qmath.h>
15#include <QtCore/qmutex.h>
17#import <AVFoundation/AVFoundation.h>
43@interface AVFMediaPlayerObserver : NSObject<AVAssetResourceLoaderDelegate>
45@property (readonly, getter=player) AVPlayer* m_player;
46@property (readonly, getter=playerItem) AVPlayerItem* m_playerItem;
47@property (readonly, getter=playerLayer) AVPlayerLayer* m_playerLayer;
48@property (readonly, getter=session) AVFMediaPlayer* m_session;
49@property (retain) AVPlayerItemTrack *videoTrack;
52- (
void) setURL:(NSURL *)url mimeType:(NSString *)mimeType;
54- (
void) prepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys;
55- (
void) assetFailedToPrepareForPlayback:(NSError *)error;
56- (
void) playerItemDidReachEnd:(NSNotification *)notification;
57- (
void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
58 change:(NSDictionary *)change context:(
void *)context;
59- (
void) detatchSession;
61- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest;
66static unsigned sessionActivationCount;
67static QMutex sessionMutex;
70@implementation AVFMediaPlayerObserver
73 AVFMediaPlayer *m_session;
75 AVPlayerItem *m_playerItem;
76 AVPlayerLayer *m_playerLayer;
78 BOOL m_bufferIsLikelyToKeepUp;
86@synthesize m_player, m_playerItem, m_playerLayer, m_session;
89- (
void)setSessionActive:(BOOL)active
91 const QMutexLocker lock(&sessionMutex);
97 if (!sessionActivationCount)
98 [AVAudioSession.sharedInstance setActive:YES error:nil];
99 ++sessionActivationCount;
102 if (!sessionActivationCount || !m_activated) {
103 qWarning(
"Unbalanced audio session deactivation, ignoring.");
106 --sessionActivationCount;
108 if (!sessionActivationCount)
109 [AVAudioSession.sharedInstance setActive:NO error:nil];
116 if (!(self = [super init]))
120 m_bufferIsLikelyToKeepUp = FALSE;
122 m_playerLayer = [AVPlayerLayer playerLayerWithPlayer:nil];
123 [m_playerLayer retain];
124 m_playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
125 m_playerLayer.anchorPoint = CGPointMake(0.0f, 0.0f);
129- (
void) setURL:(NSURL *)url mimeType:(NSString *)mimeType
134 [m_mimeType release];
135 m_mimeType = [mimeType retain];
148 BOOL isAccessing = [m_URL startAccessingSecurityScopedResource];
150 __block AVURLAsset *asset = [[AVURLAsset URLAssetWithURL:m_URL options:nil] retain];
151 [asset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()];
153 __block NSArray *requestedKeys = [[NSArray arrayWithObjects:AVF_TRACKS_KEY, AVF_PLAYABLE_KEY, nil] retain];
155 __block AVFMediaPlayerObserver *blockSelf = [self retain];
158 [asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:
160 dispatch_async( dispatch_get_main_queue(),
164 [m_URL stopAccessingSecurityScopedResource];
166 [blockSelf prepareToPlayAsset:asset withKeys:requestedKeys];
168 [requestedKeys release];
178 [m_playerItem removeObserver:self forKeyPath:@
"presentationSize"];
179 [m_playerItem removeObserver:self forKeyPath:AVF_STATUS_KEY];
180 [m_playerItem removeObserver:self forKeyPath:AVF_BUFFER_LIKELY_KEEP_UP_KEY];
181 [m_playerItem removeObserver:self forKeyPath:AVF_TRACKS_KEY];
183 [[NSNotificationCenter defaultCenter] removeObserver:self
184 name:AVPlayerItemDidPlayToEndTimeNotification
185 object:m_playerItem];
186 m_playerItem =
nullptr;
189 [m_player setRate:0.0];
190 [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_DURATION_KEY];
191 [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_KEY];
192 [m_player removeObserver:self forKeyPath:AVF_RATE_KEY];
197 m_playerLayer.player = nil;
199 [self setSessionActive:NO];
203- (
void) prepareToPlayAsset:(AVURLAsset *)asset
204 withKeys:(NSArray *)requestedKeys
210 for (NSString *thisKey in requestedKeys)
212 NSError *error = nil;
213 AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
215 qDebug() << Q_FUNC_INFO << [thisKey UTF8String] <<
" status: " << keyStatus;
217 if (keyStatus == AVKeyValueStatusFailed)
219 [self assetFailedToPrepareForPlayback:error];
226 qDebug() << Q_FUNC_INFO <<
"isPlayable: " << [asset isPlayable];
229 qWarning() <<
"Asset reported to be not playable. Playback of this asset may not be possible.";
240 m_playerItem = [AVPlayerItem playerItemWithAsset:asset];
242 qWarning() <<
"Failed to create player item";
244 NSString *localizedDescription = NSLocalizedString(@
"Item cannot be played", @
"Item cannot be played description");
245 NSString *localizedFailureReason = NSLocalizedString(@
"The assets tracks were loaded, but couldn't create player item.", @
"Item cannot be played failure reason");
246 NSDictionary *errorDict = [NSDictionary dictionaryWithObjectsAndKeys:
247 localizedDescription, NSLocalizedDescriptionKey,
248 localizedFailureReason, NSLocalizedFailureReasonErrorKey,
250 NSError *assetCannotBePlayedError = [NSError errorWithDomain:@
"StitchedStreamPlayer" code:0 userInfo:errorDict];
252 [self assetFailedToPrepareForPlayback:assetCannotBePlayedError];
257 [m_playerItem addObserver:self
258 forKeyPath:AVF_STATUS_KEY
259 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
260 context:AVFMediaPlayerObserverStatusObservationContext];
262 [m_playerItem addObserver:self
263 forKeyPath:@
"presentationSize"
264 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
265 context:AVFMediaPlayerObserverPresentationSizeContext];
267 [m_playerItem addObserver:self
268 forKeyPath:AVF_BUFFER_LIKELY_KEEP_UP_KEY
269 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
270 context:AVFMediaPlayerObserverBufferLikelyToKeepUpContext];
272 [m_playerItem addObserver:self
273 forKeyPath:AVF_TRACKS_KEY
274 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
275 context:AVFMediaPlayerObserverTracksContext];
279 [[NSNotificationCenter defaultCenter] addObserver:self
280 selector:@selector(playerItemDidReachEnd:)
281 name:AVPlayerItemDidPlayToEndTimeNotification
282 object:m_playerItem];
285 m_player = [AVPlayer playerWithPlayerItem:m_playerItem];
290 auto *audioOutput = m_session->m_audioOutput;
291 m_player.volume = (audioOutput ? audioOutput->volume : 1.);
292 m_player.muted = (audioOutput ? audioOutput->muted :
true);
293 m_session->updateAudioOutputDevice();
297 m_playerLayer.player = m_player;
302 [m_player addObserver:self
303 forKeyPath:AVF_CURRENT_ITEM_KEY
304 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
305 context:AVFMediaPlayerObserverCurrentItemObservationContext];
308 [m_player addObserver:self
309 forKeyPath:AVF_RATE_KEY
310 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
311 context:AVFMediaPlayerObserverRateObservationContext];
314 [m_player addObserver:self
315 forKeyPath:AVF_CURRENT_ITEM_DURATION_KEY
317 context:AVFMediaPlayerObserverCurrentItemDurationObservationContext];
319 [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
320 [self setSessionActive:YES];
324-(
void) assetFailedToPrepareForPlayback:(NSError *)error
327 QMetaObject::invokeMethod(m_session,
"processMediaLoadError", Qt::AutoConnection);
329 qDebug() << Q_FUNC_INFO;
330 qDebug() << [[error localizedDescription] UTF8String];
331 qDebug() << [[error localizedFailureReason] UTF8String];
332 qDebug() << [[error localizedRecoverySuggestion] UTF8String];
336- (
void) playerItemDidReachEnd:(NSNotification *)notification
338 Q_UNUSED(notification);
340 QMetaObject::invokeMethod(m_session,
"processEOS", Qt::AutoConnection);
343- (
void) observeValueForKeyPath:(NSString*) path
345 change:(NSDictionary*)change
346 context:(
void*)context
349 if (context == AVFMediaPlayerObserverStatusObservationContext)
351 AVPlayerStatus status = (AVPlayerStatus)[[change objectForKey:NSKeyValueChangeNewKey] integerValue];
356 case AVPlayerStatusUnknown:
362 case AVPlayerStatusReadyToPlay:
368 QMetaObject::invokeMethod(m_session,
"processLoadStateChange", Qt::AutoConnection);
372 case AVPlayerStatusFailed:
374 AVPlayerItem *playerItem =
static_cast<AVPlayerItem*>(object);
375 [self assetFailedToPrepareForPlayback:playerItem.error];
378 QMetaObject::invokeMethod(m_session,
"processLoadStateFailure", Qt::AutoConnection);
382 }
else if (context == AVFMediaPlayerObserverPresentationSizeContext) {
383 QSize size(m_playerItem.presentationSize.width, m_playerItem.presentationSize.height);
384 QMetaObject::invokeMethod(m_session,
"nativeSizeChanged", Qt::AutoConnection, Q_ARG(QSize, size));
385 }
else if (context == AVFMediaPlayerObserverBufferLikelyToKeepUpContext)
387 const bool isPlaybackLikelyToKeepUp = [m_playerItem isPlaybackLikelyToKeepUp];
388 if (isPlaybackLikelyToKeepUp != m_bufferIsLikelyToKeepUp) {
389 m_bufferIsLikelyToKeepUp = isPlaybackLikelyToKeepUp;
390 QMetaObject::invokeMethod(m_session,
"processBufferStateChange", Qt::AutoConnection,
391 Q_ARG(
int, isPlaybackLikelyToKeepUp ? 100 : 0));
394 else if (context == AVFMediaPlayerObserverTracksContext)
396 QMetaObject::invokeMethod(m_session,
"updateTracks", Qt::AutoConnection);
399 else if (context == AVFMediaPlayerObserverRateObservationContext)
406 else if (context == AVFMediaPlayerObserverCurrentItemObservationContext)
408 AVPlayerItem *newPlayerItem = [change objectForKey:NSKeyValueChangeNewKey];
409 if (m_playerItem != newPlayerItem)
410 m_playerItem = newPlayerItem;
412 else if (context == AVFMediaPlayerObserverCurrentItemDurationObservationContext)
414 const CMTime time = [m_playerItem duration];
415 const qint64 duration =
static_cast<qint64>(
float(time.value) /
float(time.timescale) * 1000.0f);
417 QMetaObject::invokeMethod(m_session,
"processDurationChange", Qt::AutoConnection, Q_ARG(qint64, duration));
421 [super observeValueForKeyPath:path ofObject:object change:change context:context];
425- (
void) detatchSession
428 qDebug() << Q_FUNC_INFO;
436 qDebug() << Q_FUNC_INFO;
444 [m_mimeType release];
445 [m_playerLayer release];
448 self.videoTrack = nil;
452- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
454 Q_UNUSED(resourceLoader);
456 if (![loadingRequest.request.URL.scheme isEqualToString:@
"iodevice"])
459 QIODevice *device = m_session->mediaStream();
463 device->seek(loadingRequest.dataRequest.requestedOffset);
464 if (loadingRequest.contentInformationRequest) {
465 loadingRequest.contentInformationRequest.contentType = m_mimeType;
466 loadingRequest.contentInformationRequest.contentLength = device->size();
467 loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;
470 if (loadingRequest.dataRequest) {
471 NSInteger requestedLength = loadingRequest.dataRequest.requestedLength;
472 int maxBytes = qMin(32 * 1064,
int(requestedLength));
474 buffer.resize(maxBytes);
476 NSInteger submitted = 0;
477 while (submitted < requestedLength) {
478 qint64 len = device->read(buffer.data(), maxBytes);
482 [loadingRequest.dataRequest respondWithData:[NSData dataWithBytes:buffer length:len]];
487 [loadingRequest finishLoading];
494AVFMediaPlayer::AVFMediaPlayer(QMediaPlayer *player)
496 QPlatformMediaPlayer(player),
497 m_state(QMediaPlayer::StoppedState),
498 m_mediaStatus(QMediaPlayer::NoMedia),
499 m_mediaStream(
nullptr),
501 m_requestedPosition(-1),
504 m_videoAvailable(
false),
505 m_audioAvailable(
false),
508 m_observer = [[AVFMediaPlayerObserver alloc] initWithMediaPlayerSession:
this];
509 connect(&m_playbackTimer, &QTimer::timeout,
this, &AVFMediaPlayer::processPositionChange);
510 setVideoOutput(
new AVFVideoRendererControl(
this));
516 qDebug() << Q_FUNC_INFO;
519 [m_observer detatchSession];
520 [m_observer release];
525 m_videoSink = sink ?
static_cast<
AVFVideoSink *>(sink->platformVideoSink()):
nullptr;
532 qDebug() << Q_FUNC_INFO << output;
535 if (m_videoOutput == output)
540 m_videoOutput->setLayer(
nullptr);
543 m_videoOutput = output;
545 if (m_videoOutput && m_state != QMediaPlayer::StoppedState)
546 m_videoOutput->setLayer([m_observer playerLayer]);
552 qDebug() << Q_FUNC_INFO;
554 AVAsset *currentAsset = [[m_observer playerItem] asset];
565 return m_mediaStatus;
575 return m_mediaStream;
578static void setURL(AVFMediaPlayerObserver *observer,
const QByteArray &url,
const QString &mimeType = QString())
580 NSString *urlString = [NSString stringWithUTF8String:url.constData()];
581 NSURL *nsurl = [NSURL URLWithString:urlString];
582 [observer setURL:nsurl mimeType:[NSString stringWithUTF8String:mimeType.toLatin1().constData()]];
585static void setStreamURL(AVFMediaPlayerObserver *observer,
const QByteArray &url)
587 setURL(observer, QByteArrayLiteral(
"iodevice://") + url, QFileInfo(QString::fromUtf8(url)).suffix());
593 qDebug() << Q_FUNC_INFO << content.request().url();
596 [m_observer unloadMedia];
598 m_resources = content;
601 setAudioAvailable(
false);
602 setVideoAvailable(
false);
604 m_requestedPosition = -1;
605 orientationChanged(QtVideo::Rotation::None,
false);
606 positionChanged(position());
607 if (m_duration != 0) {
611 if (!m_metaData.isEmpty()) {
615 for (
int i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
617 nativeTracks[i].clear();
621 const QMediaPlayer::MediaStatus oldMediaStatus = m_mediaStatus;
622 const QMediaPlayer::PlaybackState oldState = m_state;
624 if (!m_mediaStream && content.isEmpty()) {
625 m_mediaStatus = QMediaPlayer::NoMedia;
626 if (m_mediaStatus != oldMediaStatus)
627 mediaStatusChanged(m_mediaStatus);
629 m_state = QMediaPlayer::StoppedState;
630 if (m_state != oldState)
631 stateChanged(m_state);
636 m_mediaStatus = QMediaPlayer::LoadingMedia;
637 if (m_mediaStatus != oldMediaStatus)
638 mediaStatusChanged(m_mediaStatus);
643 if (m_mediaStream->size())
644 setStreamURL(m_observer, m_resources.toEncoded());
648 setURL(m_observer, m_resources.toEncoded());
651 m_state = QMediaPlayer::StoppedState;
652 if (m_state != oldState)
653 stateChanged(m_state);
658 AVPlayerItem *playerItem = [m_observer playerItem];
660 if (m_requestedPosition != -1)
661 return m_requestedPosition;
666 CMTime time = [playerItem currentTime];
667 return static_cast<quint64>(
float(time.value) /
float(time.timescale) * 1000.0f);
673 qDebug() << Q_FUNC_INFO;
681 qDebug() << Q_FUNC_INFO;
683 return m_bufferProgress/100.;
688 if (m_audioAvailable == available)
691 m_audioAvailable = available;
692 audioAvailableChanged(available);
697 return m_audioAvailable;
702 if (m_videoAvailable == available)
705 m_videoAvailable = available;
706 videoAvailableChanged(available);
711 return m_videoAvailable;
721 if (m_seekable == seekable)
724 m_seekable = seekable;
725 seekableChanged(seekable);
730 AVPlayerItem *playerItem = [m_observer playerItem];
733 QMediaTimeRange timeRanges;
735 NSArray *ranges = [playerItem loadedTimeRanges];
736 for (NSValue *timeRange in ranges) {
737 CMTimeRange currentTimeRange = [timeRange CMTimeRangeValue];
738 qint64 startTime = qint64(
float(currentTimeRange.start.value) / currentTimeRange.start.timescale * 1000.0);
739 timeRanges.addInterval(startTime, startTime + qint64(
float(currentTimeRange.duration.value) / currentTimeRange.duration.timescale * 1000.0));
741 if (!timeRanges.isEmpty())
744 return QMediaTimeRange(0, duration());
754 if (m_audioOutput == output)
757 m_audioOutput->q->disconnect(
this);
758 m_audioOutput = output;
760 connect(m_audioOutput->q, &QAudioOutput::deviceChanged,
this, &AVFMediaPlayer::updateAudioOutputDevice);
761 connect(m_audioOutput->q, &QAudioOutput::volumeChanged,
this, &AVFMediaPlayer::setVolume);
762 connect(m_audioOutput->q, &QAudioOutput::mutedChanged,
this, &AVFMediaPlayer::setMuted);
766 setMuted(m_audioOutput ? m_audioOutput->muted :
true);
767 setVolume(m_audioOutput ? m_audioOutput->volume : 1.);
778 qDebug() << Q_FUNC_INFO << rate;
781 if (qFuzzyCompare(m_rate, rate))
786 AVPlayer *player = [m_observer player];
787 if (player && m_state == QMediaPlayer::PlayingState)
788 [player setRate:m_rate];
790 playbackRateChanged(m_rate);
796 qDebug() << Q_FUNC_INFO << pos;
799 if (pos == position())
802 AVPlayerItem *playerItem = [m_observer playerItem];
804 m_requestedPosition = pos;
805 positionChanged(m_requestedPosition);
810 if (m_requestedPosition != -1) {
811 m_requestedPosition = -1;
812 positionChanged(position());
817 pos = qMax(qint64(0), pos);
819 pos = qMin(pos, duration());
820 m_requestedPosition = pos;
822 CMTime newTime = [playerItem currentTime];
823 newTime.value = (pos / 1000.0f) * newTime.timescale;
824 [playerItem seekToTime:newTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero
825 completionHandler:^(BOOL finished) {
827 m_requestedPosition = -1;
830 positionChanged(pos);
833 if (m_mediaStatus == QMediaPlayer::EndOfMedia) {
834 QMediaPlayer::MediaStatus newMediaStatus = (m_state == QMediaPlayer::PausedState) ? QMediaPlayer::BufferedMedia
835 : QMediaPlayer::LoadedMedia;
836 mediaStatusChanged((m_mediaStatus = newMediaStatus));
843 qDebug() << Q_FUNC_INFO <<
"currently: " << m_state;
846 if (m_mediaStatus == QMediaPlayer::NoMedia || m_mediaStatus == QMediaPlayer::InvalidMedia)
849 if (m_state == QMediaPlayer::PlayingState)
854 if (m_videoOutput && m_videoSink)
855 m_videoOutput->setLayer([m_observer playerLayer]);
858 if (m_mediaStatus == QMediaPlayer::EndOfMedia)
861 if (m_mediaStatus == QMediaPlayer::LoadedMedia || m_mediaStatus == QMediaPlayer::BufferedMedia) {
863 [[m_observer player] setRate:m_rate];
866 m_state = QMediaPlayer::PlayingState;
869 stateChanged(m_state);
870 m_playbackTimer.start(100);
876 qDebug() << Q_FUNC_INFO <<
"currently: " << m_state;
879 if (m_mediaStatus == QMediaPlayer::NoMedia)
882 if (m_state == QMediaPlayer::PausedState)
885 m_state = QMediaPlayer::PausedState;
887 if (m_videoOutput && m_videoSink)
888 m_videoOutput->setLayer([m_observer playerLayer]);
890 [[m_observer player] pause];
893 if (m_mediaStatus == QMediaPlayer::EndOfMedia)
896 positionChanged(position());
897 stateChanged(m_state);
898 m_playbackTimer.stop();
904 qDebug() << Q_FUNC_INFO <<
"currently: " << m_state;
907 if (m_state == QMediaPlayer::StoppedState)
911 [[m_observer player] pause];
915 m_videoOutput->setLayer(
nullptr);
917 if (m_mediaStatus == QMediaPlayer::BufferedMedia)
918 mediaStatusChanged((m_mediaStatus = QMediaPlayer::LoadedMedia));
920 stateChanged((m_state = QMediaPlayer::StoppedState));
921 m_playbackTimer.stop();
927 qDebug() << Q_FUNC_INFO << volume;
930 AVPlayer *player = [m_observer player];
932 player.volume = volume;
938 qDebug() << Q_FUNC_INFO << muted;
941 AVPlayer *player = [m_observer player];
943 player.muted = muted;
949 AVPlayer *player = [m_observer player];
953 if (!m_audioOutput || m_audioOutput->device.id().isEmpty()) {
956 player.audioOutputDeviceUniqueID = nil;
958 NSString *str = QString::fromUtf8(m_audioOutput->device.id()).toNSString();
959 player.audioOutputDeviceUniqueID = str;
968 [[m_observer player] setRate:m_rate];
974 qDebug() << Q_FUNC_INFO;
976 positionChanged(position());
977 m_mediaStatus = QMediaPlayer::EndOfMedia;
978 m_state = QMediaPlayer::StoppedState;
981 m_videoOutput->setLayer(
nullptr);
983 mediaStatusChanged(m_mediaStatus);
984 stateChanged(m_state);
989 AVPlayerStatus currentStatus = [[m_observer player] status];
992 qDebug() << Q_FUNC_INFO << currentStatus <<
", " << m_mediaStatus <<
", " << newState;
995 if (m_mediaStatus == QMediaPlayer::NoMedia)
998 if (currentStatus == AVPlayerStatusReadyToPlay) {
1000 AVPlayerItem *playerItem = [m_observer playerItem];
1002 applyPitchCompensation(m_pitchCompensationEnabled);
1005 m_metaData = AVFMetaData::fromAsset(playerItem.asset);
1010 setSeekable([[playerItem seekableTimeRanges] count] > 0);
1013 AVPlayerLayer *playerLayer = [m_observer playerLayer];
1014 if (m_observer.videoTrack && playerLayer) {
1015 if (!playerLayer.bounds.size.width || !playerLayer.bounds.size.height) {
1016 playerLayer.bounds = CGRectMake(0.0f, 0.0f,
1017 m_observer.videoTrack.assetTrack.naturalSize.width,
1018 m_observer.videoTrack.assetTrack.naturalSize.height);
1022 if (m_requestedPosition != -1) {
1023 setPosition(m_requestedPosition);
1024 m_requestedPosition = -1;
1028 QMediaPlayer::MediaStatus newStatus = (newState != QMediaPlayer::StoppedState)
1029 ? QMediaPlayer::BufferedMedia
1030 : QMediaPlayer::LoadedMedia;
1032 if (newStatus != m_mediaStatus)
1033 mediaStatusChanged((m_mediaStatus = newStatus));
1036 if (newState == QMediaPlayer::PlayingState && [m_observer player]) {
1038 [[m_observer player] setRate:m_rate];
1039 m_playbackTimer.start();
1046 processLoadStateChange(m_state);
1052 stateChanged((m_state = QMediaPlayer::StoppedState));
1057 if (bufferProgress == m_bufferProgress)
1060 auto status = m_mediaStatus;
1062 if (!bufferProgress) {
1063 status = QMediaPlayer::StalledMedia;
1064 }
else if (status == QMediaPlayer::StalledMedia) {
1065 status = QMediaPlayer::BufferedMedia;
1067 if (m_state == QMediaPlayer::PlayingState) {
1068 [[m_observer player] setRate:m_rate];
1069 m_playbackTimer.start();
1073 if (m_mediaStatus != status)
1074 mediaStatusChanged(m_mediaStatus = status);
1076 m_bufferProgress = bufferProgress;
1077 bufferProgressChanged(bufferProgress / 100.);
1082 if (duration == m_duration)
1085 m_duration = duration;
1086 durationChanged(duration);
1091 if (m_state == QMediaPlayer::StoppedState)
1094 positionChanged(position());
1099 if (m_requestedPosition != -1) {
1100 m_requestedPosition = -1;
1101 positionChanged(position());
1104 mediaStatusChanged((m_mediaStatus = QMediaPlayer::InvalidMedia));
1106 error(QMediaPlayer::FormatError, tr(
"Failed to load media"));
1111 setStreamURL(m_observer, m_resources.toEncoded());
1116 resetStream(
nullptr);
1121 bool firstLoad =
true;
1122 for (
int i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
1123 if (tracks[i].count())
1126 nativeTracks[i].clear();
1128 AVPlayerItem *playerItem = [m_observer playerItem];
1131 NSArray *tracks = playerItem.tracks;
1132 for (AVPlayerItemTrack *track in tracks) {
1133 AVAssetTrack *assetTrack = track.assetTrack;
1136 if ([assetTrack.mediaType isEqualToString:AVMediaTypeAudio]) {
1137 qtTrack = QPlatformMediaPlayer::AudioStream;
1138 setAudioAvailable(
true);
1139 }
else if ([assetTrack.mediaType isEqualToString:AVMediaTypeVideo]) {
1140 qtTrack = QPlatformMediaPlayer::VideoStream;
1141 setVideoAvailable(
true);
1142 if (m_observer.videoTrack != track) {
1143 m_observer.videoTrack = track;
1144 bool isMirrored =
false;
1145 QtVideo::Rotation orientation = QtVideo::Rotation::None;
1146 videoOrientationForAssetTrack(assetTrack, orientation, isMirrored);
1147 orientationChanged(orientation, isMirrored);
1150 else if ([assetTrack.mediaType isEqualToString:AVMediaTypeSubtitle]) {
1151 qtTrack = QPlatformMediaPlayer::SubtitleStream;
1153 if (qtTrack != -1) {
1154 QMediaMetaData metaData = AVFMetaData::fromAssetTrack(assetTrack);
1155 this->tracks[qtTrack].append(metaData);
1156 nativeTracks[qtTrack].append(track);
1162 setActiveTrack(SubtitleStream, -1);
1169 const auto &t = nativeTracks[type];
1170 if (type == QPlatformMediaPlayer::SubtitleStream) {
1173 AVPlayerItem *playerItem = m_observer.m_playerItem;
1175 AVAsset *asset = playerItem.asset;
1178#if defined(Q_OS_VISIONOS)
1179 [asset loadMediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible
1180 completionHandler:[=](AVMediaSelectionGroup *group, NSError *error) {
1184 auto *options = group.options;
1186 [playerItem selectMediaOption:options.firstObject inMediaSelectionGroup:group];
1189 AVMediaSelectionGroup *group = [asset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
1192 auto *options = group.options;
1194 [playerItem selectMediaOption:options.firstObject inMediaSelectionGroup:group];
1198 for (
int i = 0; i < t.count(); ++i)
1199 t.at(i).enabled = (i == index);
1200 activeTracksChanged();
1205 const auto &t = nativeTracks[type];
1206 for (
int i = 0; i < t.count(); ++i)
1207 if (t.at(i).enabled)
1214 return nativeTracks[type].count();
1217QMediaMetaData
AVFMediaPlayer::trackMetaData(QPlatformMediaPlayer::TrackType type,
int trackNumber)
1219 const auto &t = tracks[type];
1220 if (trackNumber < 0 || trackNumber >= t.count())
1221 return QMediaMetaData();
1222 return t.at(trackNumber);
1227 if (m_mediaStream) {
1228 disconnect(m_mediaStream, &QIODevice::readyRead,
this, &AVFMediaPlayer::streamReady);
1229 disconnect(m_mediaStream, &QIODevice::destroyed,
this, &AVFMediaPlayer::streamDestroyed);
1232 m_mediaStream = stream;
1234 if (m_mediaStream) {
1235 connect(m_mediaStream, &QIODevice::readyRead,
this, &AVFMediaPlayer::streamReady);
1236 connect(m_mediaStream, &QIODevice::destroyed,
this, &AVFMediaPlayer::streamDestroyed);
1242 AVPlayerItem *playerItem = [m_observer playerItem];
1245 playerItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmSpectral;
1247 playerItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmVarispeed;
1255 m_videoSink->setNativeSize(size);
1258void AVFMediaPlayer::orientationChanged(QtVideo::Rotation rotation,
bool mirrored)
1263 m_videoOutput->setVideoRotation(rotation);
1268 QtVideo::Rotation &angle,
1271 angle = QtVideo::Rotation::None;
1273 CGAffineTransform transform = videoTrack.preferredTransform;
1274 if (CGAffineTransformIsIdentity(transform))
1276 qreal delta = transform.a * transform.d - transform.b * transform.c;
1277 qreal radians = qAtan2(transform.b, transform.a);
1278 qreal degrees = qRadiansToDegrees(radians);
1279 qreal scaleX = (transform.a/qAbs(transform.a)) * qSqrt(qPow(transform.a, 2) + qPow(transform.c, 2));
1280 qreal scaleY = (transform.d/abs(transform.d)) * qSqrt(qPow(transform.b, 2) + qPow(transform.d, 2));
1286 }
else if (scaleY < 0.0) {
1288 degrees = (180 + (
int)degrees) % 360;
1293 if (qFuzzyCompare(degrees, qreal(90)) || qFuzzyCompare(degrees, qreal(-270))) {
1294 angle = QtVideo::Rotation::Clockwise90;
1295 }
else if (qFuzzyCompare(degrees, qreal(-90)) || qFuzzyCompare(degrees, qreal(270))) {
1296 angle = QtVideo::Rotation::Clockwise270;
1297 }
else if (qFuzzyCompare(degrees, qreal(180)) || qFuzzyCompare(degrees, qreal(-180))) {
1298 angle = QtVideo::Rotation::Clockwise180;
1305 if (m_pitchCompensationEnabled == enabled)
1308 applyPitchCompensation(enabled);
1310 m_pitchCompensationEnabled = enabled;
1311 pitchCompensationChanged(enabled);
1316 return m_pitchCompensationEnabled;
1322 return QPlatformMediaPlayer::PitchCompensationAvailability::Available;
1325#include "moc_avfmediaplayer_p.cpp"
void setVideoMirrored(bool mirrored)
void setVideoSink(AVFVideoSink *sink)