Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
avfmediaplayer.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <avfvideosink_p.h>
7#include <common/avfmetadata_p.h>
8#include <mediaplayer/avfvideorenderercontrol_p.h>
9
10#include <QtMultimedia/qaudiooutput.h>
11#include <QtMultimedia/private/qplatformaudiooutput_p.h>
12
13#include <QtCore/qdir.h>
14#include <QtCore/qfileinfo.h>
15#include <QtCore/qmath.h>
16#include <QtCore/qmimedatabase.h>
17#include <QtCore/qmutex.h>
18#include <QtCore/qpointer.h>
19#include <QtCore/qthread.h>
20#include <QtCore/private/qexpected_p.h>
21
22#import <AVFoundation/AVFoundation.h>
23
24QT_USE_NAMESPACE
25
26//AVAsset Keys
27static NSString* const AVF_TRACKS_KEY = @"tracks";
28static NSString* const AVF_PLAYABLE_KEY = @"playable";
29
30//AVPlayerItem keys
31static NSString* const AVF_STATUS_KEY = @"status";
32static NSString* const AVF_BUFFER_LIKELY_KEEP_UP_KEY = @"playbackLikelyToKeepUp";
33
34//AVPlayer keys
35static NSString* const AVF_RATE_KEY = @"rate";
36static NSString* const AVF_CURRENT_ITEM_KEY = @"currentItem";
37static NSString* const AVF_CURRENT_ITEM_DURATION_KEY = @"currentItem.duration";
38
47
48@interface AVFMediaPlayerObserver : NSObject<AVAssetResourceLoaderDelegate>
49
50@property (readonly, getter=player) AVPlayer* m_player;
51@property (readonly, getter=playerItem) AVPlayerItem* m_playerItem;
52@property (readonly, getter=playerLayer) AVPlayerLayer* m_playerLayer;
53@property (retain) AVPlayerItemTrack *videoTrack;
54
55- (AVFMediaPlayerObserver *) initWithMediaPlayerSession:(AVFMediaPlayer *)session;
56- (void) setURL:(NSURL *)url mimeType:(NSString *)mimeType;
57- (void) unloadMedia;
58- (void) prepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys;
59- (void) assetFailedToPrepareForPlayback:(NSError *)error;
60- (void) playerItemDidReachEnd:(NSNotification *)notification;
61- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
62 change:(NSDictionary *)change context:(void *)context;
63- (void) clearSession;
64- (void) notifySeekComplete;
65- (void) dealloc;
66- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest;
67@end
68
69#ifdef Q_OS_IOS
70// Alas, no such thing as 'class variable', hence globals:
71static unsigned sessionActivationCount;
72static QMutex sessionMutex;
73#endif // Q_OS_IOS
74
75namespace {
76
77struct GuardedPlatformPlayer
78{
79 mutable QMutex mutex;
80 AVFMediaPlayer *player{};
81
82 explicit operator bool() const
83 {
84 std::lock_guard guard(mutex);
85 return player;
86 }
87
88 struct not_a_platform_player_t
89 {
90 };
91
92 template <typename Functor>
93 auto withPlatformPlayer(Functor &&f)
94 -> q23::expected<std::invoke_result_t<Functor, AVFMediaPlayer *>,
95 not_a_platform_player_t>
96 {
97 std::unique_lock guard(mutex);
98 if (!player)
99 return q23::unexpected{ not_a_platform_player_t{} };
100 if constexpr (std::is_void_v<std::invoke_result_t<Functor, AVFMediaPlayer *>>) {
101 f(player);
102 return {};
103 } else {
104 return f(player);
105 }
106 }
107
108 template <typename Functor>
109 void invokeWithPlatformPlayer(Functor f)
110 {
111 std::unique_lock guard(mutex);
112 if (!player)
113 return;
114
115 if (player->thread()->isCurrentThread()) {
116 guard.unlock();
117 f(player);
118 } else {
119 QMetaObject::invokeMethod(player, [f = std::move(f), player = player]() {
120 f(player);
121 });
122 }
123 }
124
125 void clear()
126 {
127 std::lock_guard<QMutex> guard(mutex);
128 player = nullptr;
129 }
130};
131} // namespace
132
133@implementation AVFMediaPlayerObserver {
134@private
135 GuardedPlatformPlayer m_platformPlayer;
136 AVPlayer *m_player;
137 AVPlayerItem *m_playerItem;
138 AVPlayerLayer *m_playerLayer;
139 NSURL *m_URL;
140 BOOL m_bufferIsLikelyToKeepUp;
141 NSData *m_data;
142 NSString *m_mimeType;
143#ifdef Q_OS_IOS
144 BOOL m_activated;
145#endif
146}
147
148@synthesize m_player, m_playerItem, m_playerLayer;
149
150#ifdef Q_OS_IOS
151- (void)setSessionActive:(BOOL)active
152{
153 const QMutexLocker lock(&sessionMutex);
154 if (active) {
155 // Don't count the same player twice if already activated,
156 // unless it tried to deactivate first:
157 if (m_activated)
158 return;
159 if (!sessionActivationCount)
160 [AVAudioSession.sharedInstance setActive:YES error:nil];
161 ++sessionActivationCount;
162 m_activated = YES;
163 } else {
164 if (!sessionActivationCount || !m_activated) {
165 qWarning("Unbalanced audio session deactivation, ignoring.");
166 return;
167 }
168 --sessionActivationCount;
169 m_activated = NO;
170 if (!sessionActivationCount)
171 [AVAudioSession.sharedInstance setActive:NO error:nil];
172 }
173}
174#endif // Q_OS_IOS
175
176- (AVFMediaPlayerObserver *) initWithMediaPlayerSession:(AVFMediaPlayer *)session
177{
178 if (!(self = [super init]))
179 return nil;
180 m_platformPlayer.player = session;
181 m_bufferIsLikelyToKeepUp = FALSE;
182
183 m_playerLayer = [AVPlayerLayer playerLayerWithPlayer:nil];
184 [m_playerLayer retain];
185 m_playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
186 m_playerLayer.anchorPoint = CGPointMake(0.0f, 0.0f);
187 return self;
188}
189
190- (void) setURL:(NSURL *)url mimeType:(NSString *)mimeType
191{
192 [m_mimeType release];
193 m_mimeType = [mimeType retain];
194
195 if (m_URL != url)
196 {
197 [m_URL release];
198 m_URL = [url copy];
199
200 //Create an asset for inspection of a resource referenced by a given URL.
201 //Load the values for the asset keys "tracks", "playable".
202
203 // use __block to avoid maintaining strong references on variables captured by the
204 // following block callback
205 __block BOOL isAccessing = [m_URL startAccessingSecurityScopedResource];
206 __block AVURLAsset *asset = [[AVURLAsset URLAssetWithURL:m_URL options:nil] retain];
207 [asset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()];
208
209 __block NSArray *requestedKeys = [[NSArray arrayWithObjects:AVF_TRACKS_KEY, AVF_PLAYABLE_KEY, nil] retain];
210
211 __block AVFMediaPlayerObserver *blockSelf = [self retain];
212
213 // Tells the asset to load the values of any of the specified keys that are not already loaded.
214 [asset loadValuesAsynchronouslyForKeys:requestedKeys
215 completionHandler:^{
216 dispatch_async(dispatch_get_main_queue(), ^{
217 if (isAccessing)
218 [m_URL stopAccessingSecurityScopedResource];
219 [blockSelf prepareToPlayAsset:asset withKeys:requestedKeys];
220 [asset release];
221 [requestedKeys release];
222 [blockSelf release];
223 });
224 }];
225 }
226}
227
228- (void) unloadMedia
229{
230 if (m_playerItem) {
231 [m_playerItem removeObserver:self forKeyPath:@"presentationSize"];
232 [m_playerItem removeObserver:self forKeyPath:AVF_STATUS_KEY];
233 [m_playerItem removeObserver:self forKeyPath:AVF_BUFFER_LIKELY_KEEP_UP_KEY];
234 [m_playerItem removeObserver:self forKeyPath:AVF_TRACKS_KEY];
235
236 [[NSNotificationCenter defaultCenter] removeObserver:self
237 name:AVPlayerItemDidPlayToEndTimeNotification
238 object:m_playerItem];
239 m_playerItem = nullptr;
240 }
241 if (m_player) {
242 [m_player setRate:0.0];
243 [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_DURATION_KEY];
244 [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_KEY];
245 [m_player removeObserver:self forKeyPath:AVF_RATE_KEY];
246 [m_player replaceCurrentItemWithPlayerItem:nil];
247
248 // Defer the release of AVPlayer to allow CoreMedia/VideoToolbox
249 // dispatch queues to finish pending operations. Releasing the
250 // player synchronously can cause sporadic crashes on macOS 14
251 // when background threads still reference internal resources.
252 AVPlayer *player = m_player;
253 m_player = nullptr;
254 dispatch_async(dispatch_get_main_queue(), ^{
255 [player release];
256 });
257 }
258 if (m_playerLayer)
259 m_playerLayer.player = nil;
260#if defined(Q_OS_IOS)
261 [self setSessionActive:NO];
262#endif
263}
264
265- (void) prepareToPlayAsset:(AVURLAsset *)asset
266 withKeys:(NSArray *)requestedKeys
267{
268 if (!m_platformPlayer)
269 return;
270
271 //Make sure that the value of each key has loaded successfully.
272 for (NSString *thisKey in requestedKeys)
273 {
274 NSError *error = nil;
275 AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
276#ifdef QT_DEBUG_AVF
277 qDebug() << Q_FUNC_INFO << [thisKey UTF8String] << " status: " << keyStatus;
278#endif
279 if (keyStatus == AVKeyValueStatusFailed)
280 {
281 [self assetFailedToPrepareForPlayback:error];
282 return;
283 }
284 }
285
286 //Use the AVAsset playable property to detect whether the asset can be played.
287#ifdef QT_DEBUG_AVF
288 qDebug() << Q_FUNC_INFO << "isPlayable: " << [asset isPlayable];
289#endif
290 if (!asset.playable)
291 qWarning() << "Asset reported to be not playable. Playback of this asset may not be possible.";
292
293 //At this point we're ready to set up for playback of the asset.
294 //Stop observing our prior AVPlayerItem, if we have one.
295 if (m_playerItem)
296 {
297 //Remove existing player item key value observers and notifications.
298 [self unloadMedia];
299 }
300
301 //Create a new instance of AVPlayerItem from the now successfully loaded AVAsset.
302 m_playerItem = [AVPlayerItem playerItemWithAsset:asset];
303 if (!m_playerItem) {
304 qWarning() << "Failed to create player item";
305 //Generate an error describing the failure.
306 NSString *localizedDescription = NSLocalizedString(@"Item cannot be played", @"Item cannot be played description");
307 NSString *localizedFailureReason = NSLocalizedString(@"The assets tracks were loaded, but couldn't create player item.", @"Item cannot be played failure reason");
308 NSDictionary *errorDict = [NSDictionary dictionaryWithObjectsAndKeys:
309 localizedDescription, NSLocalizedDescriptionKey,
310 localizedFailureReason, NSLocalizedFailureReasonErrorKey,
311 nil];
312 NSError *assetCannotBePlayedError = [NSError errorWithDomain:@"StitchedStreamPlayer" code:0 userInfo:errorDict];
313
314 [self assetFailedToPrepareForPlayback:assetCannotBePlayedError];
315 return;
316 }
317
318 //Observe the player item "status" key to determine when it is ready to play.
319 [m_playerItem addObserver:self
320 forKeyPath:AVF_STATUS_KEY
321 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
322 context:AVFMediaPlayerObserverStatusObservationContext];
323
324 [m_playerItem addObserver:self
325 forKeyPath:@"presentationSize"
326 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
327 context:AVFMediaPlayerObserverPresentationSizeContext];
328
329 [m_playerItem addObserver:self
330 forKeyPath:AVF_BUFFER_LIKELY_KEEP_UP_KEY
331 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
332 context:AVFMediaPlayerObserverBufferLikelyToKeepUpContext];
333
334 [m_playerItem addObserver:self
335 forKeyPath:AVF_TRACKS_KEY
336 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
337 context:AVFMediaPlayerObserverTracksContext];
338
339 //When the player item has played to its end time we'll toggle
340 //the movie controller Pause button to be the Play button
341 [[NSNotificationCenter defaultCenter] addObserver:self
342 selector:@selector(playerItemDidReachEnd:)
343 name:AVPlayerItemDidPlayToEndTimeNotification
344 object:m_playerItem];
345
346 //Get a new AVPlayer initialized to play the specified player item.
347 m_player = [AVPlayer playerWithPlayerItem:m_playerItem];
348 [m_player retain];
349
350 //Set the initial audio ouptut settings on new player object
351 {
352 m_platformPlayer.withPlatformPlayer([&](AVFMediaPlayer *player) {
353 auto *audioOutput = player->m_audioOutput;
354 m_player.volume = (audioOutput ? audioOutput->volume : 1.);
355 m_player.muted = (audioOutput ? audioOutput->muted : true);
356 player->updateAudioOutputDevice();
357 });
358 }
359
360 //Assign the output layer to the new player
361 m_playerLayer.player = m_player;
362
363 //Observe the AVPlayer "currentItem" property to find out when any
364 //AVPlayer replaceCurrentItemWithPlayerItem: replacement will/did
365 //occur.
366 [m_player addObserver:self
367 forKeyPath:AVF_CURRENT_ITEM_KEY
368 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
369 context:AVFMediaPlayerObserverCurrentItemObservationContext];
370
371 //Observe the AVPlayer "rate" property to update the scrubber control.
372 [m_player addObserver:self
373 forKeyPath:AVF_RATE_KEY
374 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
375 context:AVFMediaPlayerObserverRateObservationContext];
376
377 //Observe the duration for getting the buffer state
378 [m_player addObserver:self
379 forKeyPath:AVF_CURRENT_ITEM_DURATION_KEY
380 options:0
381 context:AVFMediaPlayerObserverCurrentItemDurationObservationContext];
382#if defined(Q_OS_IOS)
383 [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
384 [self setSessionActive:YES];
385#endif
386}
387
388-(void) assetFailedToPrepareForPlayback:(NSError *)error
389{
390 QMediaPlayer::Error errorCode = QMediaPlayer::FormatError;
391 if (error) {
392 NSError *underlyingError = error.userInfo[NSUnderlyingErrorKey];
393 if (underlyingError && ![underlyingError.domain isEqualToString:AVFoundationErrorDomain])
394 errorCode = QMediaPlayer::ResourceError;
395 }
396 m_platformPlayer.invokeWithPlatformPlayer([errorCode](AVFMediaPlayer *platformPlayer) {
397 platformPlayer->processMediaLoadError(errorCode);
398 });
399
400#ifdef QT_DEBUG_AVF
401 qDebug() << Q_FUNC_INFO;
402 qDebug() << [[error localizedDescription] UTF8String];
403 qDebug() << [[error localizedFailureReason] UTF8String];
404 qDebug() << [[error localizedRecoverySuggestion] UTF8String];
405#endif
406}
407
408- (void) playerItemDidReachEnd:(NSNotification *)notification
409{
410 Q_UNUSED(notification);
411
412 m_platformPlayer.invokeWithPlatformPlayer([](AVFMediaPlayer *platformPlayer) {
413 platformPlayer->processEOS();
414 });
415}
416
417- (void) observeValueForKeyPath:(NSString*) path
418 ofObject:(id)object
419 change:(NSDictionary*)change
420 context:(void*)context
421{
422 //AVPlayerItem "status" property value observer.
423 if (context == AVFMediaPlayerObserverStatusObservationContext)
424 {
425 AVPlayerStatus status = (AVPlayerStatus)[[change objectForKey:NSKeyValueChangeNewKey] integerValue];
426 switch (status)
427 {
428 //Indicates that the status of the player is not yet known because
429 //it has not tried to load new media resources for playback
430 case AVPlayerStatusUnknown:
431 {
432 //QMetaObject::invokeMethod(m_session, "processLoadStateChange", Qt::AutoConnection);
433 }
434 break;
435
436 case AVPlayerStatusReadyToPlay:
437 {
438 //Once the AVPlayerItem becomes ready to play, i.e.
439 //[playerItem status] == AVPlayerItemStatusReadyToPlay,
440 //its duration can be fetched from the item.
441
442 m_platformPlayer.invokeWithPlatformPlayer([](AVFMediaPlayer *platformPlayer) {
443 platformPlayer->processLoadStateChange();
444 });
445 }
446 break;
447
448 case AVPlayerStatusFailed:
449 {
450 AVPlayerItem *playerItem = static_cast<AVPlayerItem*>(object);
451 [self assetFailedToPrepareForPlayback:playerItem.error];
452
453 m_platformPlayer.invokeWithPlatformPlayer([](AVFMediaPlayer *platformPlayer) {
454 platformPlayer->processLoadStateChange();
455 });
456 }
457 break;
458 }
459 } else if (context == AVFMediaPlayerObserverPresentationSizeContext) {
460 QSize size(m_playerItem.presentationSize.width, m_playerItem.presentationSize.height);
461 m_platformPlayer.invokeWithPlatformPlayer([size](AVFMediaPlayer *platformPlayer) {
462 platformPlayer->nativeSizeChanged(size);
463 });
464 } else if (context == AVFMediaPlayerObserverBufferLikelyToKeepUpContext)
465 {
466 const bool isPlaybackLikelyToKeepUp = [m_playerItem isPlaybackLikelyToKeepUp];
467 if (isPlaybackLikelyToKeepUp != m_bufferIsLikelyToKeepUp) {
468 m_bufferIsLikelyToKeepUp = isPlaybackLikelyToKeepUp;
469 int bufferProgress = isPlaybackLikelyToKeepUp ? 100 : 0;
470
471 m_platformPlayer.invokeWithPlatformPlayer(
472 [bufferProgress](AVFMediaPlayer *platformPlayer) {
473 platformPlayer->processBufferStateChange(bufferProgress);
474 });
475 }
476 }
477 else if (context == AVFMediaPlayerObserverTracksContext)
478 {
479 m_platformPlayer.invokeWithPlatformPlayer([](AVFMediaPlayer *platformPlayer) {
480 platformPlayer->updateTracks();
481 });
482 }
483 //AVPlayer "rate" property value observer.
484 else if (context == AVFMediaPlayerObserverRateObservationContext) {
485 //QMetaObject::invokeMethod(m_session, "setPlaybackRate", Qt::AutoConnection, Q_ARG(qreal, [m_player rate]));
486 }
487 //AVPlayer "currentItem" property observer.
488 //Called when the AVPlayer replaceCurrentItemWithPlayerItem:
489 //replacement will/did occur.
490 else if (context == AVFMediaPlayerObserverCurrentItemObservationContext) {
491 AVPlayerItem *newPlayerItem = [change objectForKey:NSKeyValueChangeNewKey];
492 if (m_playerItem != newPlayerItem)
493 m_playerItem = newPlayerItem;
494 } else if (context == AVFMediaPlayerObserverCurrentItemDurationObservationContext) {
495 const CMTime time = [m_playerItem duration];
496 const qint64 dur = static_cast<qint64>(float(time.value) / float(time.timescale) * 1000.0f);
497
498 m_platformPlayer.invokeWithPlatformPlayer([dur](AVFMediaPlayer *platformPlayer) {
499 platformPlayer->processDurationChange(dur);
500 });
501 } else {
502 [super observeValueForKeyPath:path ofObject:object change:change context:context];
503 }
504}
505
506- (void)clearSession
507{
508#ifdef QT_DEBUG_AVF
509 qDebug() << Q_FUNC_INFO;
510#endif
511 m_platformPlayer.clear();
512}
513
514- (void)notifySeekComplete
515{
516 m_platformPlayer.withPlatformPlayer([](AVFMediaPlayer *player) {
517 player->seekCompleted();
518 });
519}
520
521- (void) dealloc
522{
523#ifdef QT_DEBUG_AVF
524 qDebug() << Q_FUNC_INFO;
525#endif
526 [self unloadMedia];
527
528 m_platformPlayer.clear();
529
530 if (m_URL) {
531 [m_URL release];
532 }
533
534 [m_mimeType release];
535 [m_playerLayer release];
536 // 'videoTrack' is a 'retain' property, but still needs a
537 // manual 'release' (i.e. setting to nil):
538 self.videoTrack = nil;
539 [super dealloc];
540}
541
542- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
543{
544 Q_UNUSED(resourceLoader);
545
546 if (![loadingRequest.request.URL.scheme isEqualToString:@"iodevice"])
547 return NO;
548
549 auto result = m_platformPlayer.withPlatformPlayer([&](AVFMediaPlayer *platformPlayer) {
550 QIODevice *device = platformPlayer->mediaStream();
551 if (!device)
552 return NO;
553
554 device->seek(loadingRequest.dataRequest.requestedOffset);
555 if (loadingRequest.contentInformationRequest) {
556 loadingRequest.contentInformationRequest.contentType = m_mimeType;
557 loadingRequest.contentInformationRequest.contentLength = device->size();
558 loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;
559 }
560
561 if (loadingRequest.dataRequest) {
562 NSInteger requestedLength = loadingRequest.dataRequest.requestedLength;
563 int maxBytes = qMin(32 * 1024, int(requestedLength));
564 QByteArray buffer;
565 buffer.resize(maxBytes);
566
567 NSInteger submitted = 0;
568 while (submitted < requestedLength) {
569 qint64 len = device->read(buffer.data(), maxBytes);
570 if (len < 1)
571 break;
572
573 [loadingRequest.dataRequest respondWithData:buffer.toNSData()];
574 submitted += len;
575 }
576
577 // Finish loading even if not all bytes submitted.
578 [loadingRequest finishLoading];
579 }
580
581 return YES;
582 });
583
584 return result.value_or(NO);
585}
586@end
587
588AVFMediaPlayer::AVFMediaPlayer(QMediaPlayer *player)
589 : QObject(player),
590 QPlatformMediaPlayer(player),
591 m_mediaStream(nullptr),
592 m_rate(1.0),
593 m_requestedPosition(-1),
594 m_duration(0),
595 m_bufferProgress(0)
596{
597 m_observer = [[AVFMediaPlayerObserver alloc] initWithMediaPlayerSession:this];
598 connect(&m_playbackTimer, &QTimer::timeout, this, &AVFMediaPlayer::processPositionChange);
599 setVideoOutput(new AVFVideoRendererControl(this));
600}
601
603{
604#ifdef QT_DEBUG_AVF
605 qDebug() << Q_FUNC_INFO;
606#endif
607
608 // Unload media before the C++ side is torn down, so that
609 // CoreMedia/VideoToolbox threads don't outlive our objects.
610 [m_observer unloadMedia];
611
612 //Detatch the session from the sessionObserver (which could still be alive trying to communicate with this session).
613 [m_observer clearSession];
614 [m_observer release];
615}
616
617void AVFMediaPlayer::setVideoSink(QVideoSink *sink)
618{
619 m_videoSink = sink ? static_cast<AVFVideoSink *>(sink->platformVideoSink()): nullptr;
620 m_videoOutput->setVideoSink(m_videoSink);
621}
622
624{
625#ifdef QT_DEBUG_AVF
626 qDebug() << Q_FUNC_INFO << output;
627#endif
628
629 if (m_videoOutput == output)
630 return;
631
632 //Set the current output layer to null to stop rendering
633 if (m_videoOutput) {
634 m_videoOutput->setLayer(nullptr);
635 }
636
637 m_videoOutput = output;
638
639 if (m_videoOutput && state() != QMediaPlayer::StoppedState)
640 m_videoOutput->setLayer([m_observer playerLayer]);
641}
642
644{
645#ifdef QT_DEBUG_AVF
646 qDebug() << Q_FUNC_INFO;
647#endif
648 AVAsset *currentAsset = [[m_observer playerItem] asset];
649 return currentAsset;
650}
651
653{
654 return m_resources;
655}
656
658{
659 return m_mediaStream;
660}
661
662static void setURL(AVFMediaPlayerObserver *observer, const QUrl &url, const QString &mimeType = QString())
663{
664 QUrl resolvedUrl = url;
665 // AVFoundation cannot handle file URLs with a relative path
666 if (url.isLocalFile() && !QDir::isAbsolutePath(url.path()))
667 resolvedUrl = QUrl::fromLocalFile(QFileInfo(url.path()).absoluteFilePath());
668 NSURL *nsurl = resolvedUrl.toNSURL();
669 [observer setURL:nsurl mimeType:[NSString stringWithUTF8String:mimeType.toLatin1().constData()]];
670}
671
672
673void AVFMediaPlayer::setMedia(const QUrl &content, QIODevice *stream)
674{
675#ifdef QT_DEBUG_AVF
676 qDebug() << Q_FUNC_INFO << content.request().url();
677#endif
678
679 [m_observer unloadMedia];
680
681 m_resources = content;
682 resetStream(stream);
683
684 m_requestedPosition = -1;
685 orientationChanged(QtVideo::Rotation::None, false);
686 positionChanged(position());
687 if (m_duration != 0) {
688 m_duration = 0;
689 durationChanged(0);
690 }
691 if (!m_metaData.isEmpty()) {
692 m_metaData.clear();
693 metaDataChanged();
694 }
695 resetBufferProgress();
696 for (int i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
697 tracks[i].clear();
698 nativeTracks[i].clear();
699 }
700 tracksChanged();
701
702 if (!m_mediaStream && content.isEmpty()) {
703 seekableChanged(false);
704 audioAvailableChanged(false);
705 videoAvailableChanged(false);
706
707 mediaStatusChanged(QMediaPlayer::NoMedia);
708 stateChanged(QMediaPlayer::StoppedState);
709
710 return;
711 }
712
713 mediaStatusChanged(QMediaPlayer::LoadingMedia);
714
715 if (m_mediaStream) {
716 // If there is a data, try to load it,
717 // otherwise wait for readyRead.
718 if (m_mediaStream->size())
720 } else {
721 //Load AVURLAsset
722 //initialize asset using content's URL
723 setURL(m_observer, m_resources);
724 }
725
726 stateChanged(QMediaPlayer::StoppedState);
727}
728
730{
731 AVPlayerItem *playerItem = [m_observer playerItem];
732
733 if (m_requestedPosition != -1)
734 return m_requestedPosition;
735
736 if (!playerItem)
737 return 0;
738
739 CMTime time = [playerItem currentTime];
740 return static_cast<quint64>(float(time.value) / float(time.timescale) * 1000.0f);
741}
742
744{
745#ifdef QT_DEBUG_AVF
746 qDebug() << Q_FUNC_INFO;
747#endif
748 return m_duration;
749}
750
752{
753#ifdef QT_DEBUG_AVF
754 qDebug() << Q_FUNC_INFO;
755#endif
756 return m_bufferProgress/100.;
757}
758
760{
761 AVPlayerItem *playerItem = [m_observer playerItem];
762
763 if (!playerItem)
764 return {};
765
766 if (state() == QMediaPlayer::StoppedState)
767 return {};
768
769 QMediaTimeRange timeRanges;
770
771 NSArray *ranges = [playerItem loadedTimeRanges];
772 for (NSValue *timeRange in ranges) {
773 CMTimeRange currentTimeRange = [timeRange CMTimeRangeValue];
774 qint64 startTime = qint64(float(currentTimeRange.start.value) / currentTimeRange.start.timescale * 1000.0);
775 timeRanges.addInterval(startTime, startTime + qint64(float(currentTimeRange.duration.value) / currentTimeRange.duration.timescale * 1000.0));
776 }
777 return timeRanges;
778}
779
781{
782 return m_rate;
783}
784
785void AVFMediaPlayer::setAudioOutput(QPlatformAudioOutput *output)
786{
787 if (m_audioOutput == output)
788 return;
789 if (m_audioOutput)
790 m_audioOutput->q->disconnect(this);
791 m_audioOutput = output;
792 if (m_audioOutput) {
793 connect(m_audioOutput->q, &QAudioOutput::deviceChanged, this, &AVFMediaPlayer::updateAudioOutputDevice);
794 connect(m_audioOutput->q, &QAudioOutput::volumeChanged, this, &AVFMediaPlayer::setVolume);
795 connect(m_audioOutput->q, &QAudioOutput::mutedChanged, this, &AVFMediaPlayer::setMuted);
796 //connect(m_audioOutput->q, &QAudioOutput::audioRoleChanged, this, &AVFMediaPlayer::setAudioRole);
797 }
799 setMuted(m_audioOutput ? m_audioOutput->muted : true);
800 setVolume(m_audioOutput ? m_audioOutput->volume : 1.);
801}
802
804{
805 return m_metaData;
806}
807
808void AVFMediaPlayer::setPlaybackRate(qreal rate)
809{
810#ifdef QT_DEBUG_AVF
811 qDebug() << Q_FUNC_INFO << rate;
812#endif
813
814 if (QtPrivate::fuzzyCompare(m_rate, rate))
815 return;
816
817 m_rate = rate;
818
819 AVPlayer *player = [m_observer player];
820 if (player && state() == QMediaPlayer::PlayingState)
821 [player setRate:m_rate];
822
823 playbackRateChanged(m_rate);
824}
825
827{
828#ifdef QT_DEBUG_AVF
829 qDebug() << Q_FUNC_INFO << pos;
830#endif
831
832 if (pos == position())
833 return;
834
835 AVPlayerItem *playerItem = [m_observer playerItem];
836 if (!playerItem) {
837 m_requestedPosition = pos;
838 positionChanged(m_requestedPosition);
839 return;
840 }
841
842 if (!isSeekable()) {
843 if (m_requestedPosition != -1) {
844 m_requestedPosition = -1;
845 positionChanged(position());
846 }
847 return;
848 }
849
850 pos = qMax(qint64(0), pos);
851 if (duration() > 0)
852 pos = qMin(pos, duration());
853 m_requestedPosition = pos;
854
855 CMTime newTime = [playerItem currentTime];
856 newTime.value = (pos / 1000.0f) * newTime.timescale;
857 AVFMediaPlayerObserver *observer = m_observer;
858 [playerItem seekToTime:newTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero
859 completionHandler:^(BOOL finished) {
860 if (finished)
861 [observer notifySeekComplete];
862 }];
863
864 positionChanged(pos);
865
866 // Reset media status if the current status is EndOfMedia
867 if (mediaStatus() == QMediaPlayer::EndOfMedia) {
868 QMediaPlayer::MediaStatus newMediaStatus = (state() == QMediaPlayer::PausedState)
869 ? QMediaPlayer::BufferedMedia
870 : QMediaPlayer::LoadedMedia;
871 mediaStatusChanged(newMediaStatus);
872 }
873}
874
876{
877#ifdef QT_DEBUG_AVF
878 qDebug() << Q_FUNC_INFO << "currently: " << state();
879#endif
880
881 if (mediaStatus() == QMediaPlayer::NoMedia || mediaStatus() == QMediaPlayer::InvalidMedia)
882 return;
883
884 if (state() == QMediaPlayer::PlayingState)
885 return;
886
887 if (state() != QMediaPlayer::PausedState)
888 resetCurrentLoop();
889
890 if (m_videoOutput && m_videoSink)
891 m_videoOutput->setLayer([m_observer playerLayer]);
892
893 // Reset media status if the current status is EndOfMedia
894 if (mediaStatus() == QMediaPlayer::EndOfMedia)
895 setPosition(0);
896
897 if (mediaStatus() == QMediaPlayer::LoadedMedia
898 || mediaStatus() == QMediaPlayer::BufferedMedia) {
899 // Setting the rate starts playback
900 [[m_observer player] setRate:m_rate];
901 }
902
903 processLoadStateChange(QMediaPlayer::PlayingState);
904
905 stateChanged(QMediaPlayer::PlayingState);
906 m_playbackTimer.start(100);
907}
908
910{
911#ifdef QT_DEBUG_AVF
912 qDebug() << Q_FUNC_INFO << "currently: " << state();
913#endif
914
915 if (mediaStatus() == QMediaPlayer::NoMedia || mediaStatus() == QMediaPlayer::InvalidMedia)
916 return;
917
918 if (state() == QMediaPlayer::PausedState)
919 return;
920
921 stateChanged(QMediaPlayer::PausedState);
922
923 if (m_videoOutput && m_videoSink)
924 m_videoOutput->setLayer([m_observer playerLayer]);
925
926 [[m_observer player] pause];
927
928 // Reset media status if the current status is EndOfMedia
929 if (mediaStatus() == QMediaPlayer::EndOfMedia)
930 setPosition(0);
931
932 positionChanged(position());
933 m_playbackTimer.stop();
934}
935
937{
938#ifdef QT_DEBUG_AVF
939 qDebug() << Q_FUNC_INFO << "currently: " << state();
940#endif
941
942 if (state() == QMediaPlayer::StoppedState && mediaStatus() != QMediaPlayer::EndOfMedia)
943 return;
944
945 // AVPlayer doesn't have stop(), only pause() and play().
946 [[m_observer player] pause];
947 setPosition(0);
948
949 if (m_videoOutput)
950 m_videoOutput->setLayer(nullptr);
951
952 resetBufferProgress();
953
954 if (mediaStatus() == QMediaPlayer::BufferedMedia || mediaStatus() == QMediaPlayer::EndOfMedia)
955 mediaStatusChanged(QMediaPlayer::LoadedMedia);
956
957 stateChanged(QMediaPlayer::StoppedState);
958 m_playbackTimer.stop();
959}
960
961void AVFMediaPlayer::setVolume(float volume)
962{
963#ifdef QT_DEBUG_AVF
964 qDebug() << Q_FUNC_INFO << volume;
965#endif
966
967 AVPlayer *player = [m_observer player];
968 if (player)
969 player.volume = volume;
970}
971
972void AVFMediaPlayer::setMuted(bool muted)
973{
974#ifdef QT_DEBUG_AVF
975 qDebug() << Q_FUNC_INFO << muted;
976#endif
977
978 AVPlayer *player = [m_observer player];
979 if (player)
980 player.muted = muted;
981}
982
984{
985#ifdef Q_OS_MACOS
986 AVPlayer *player = [m_observer player];
987 if (!player)
988 return;
989
990 if (!m_audioOutput || m_audioOutput->device.id().isEmpty()) {
991 if (!m_audioOutput)
992 player.muted = true;
993 player.audioOutputDeviceUniqueID = nil;
994 } else {
995 NSString *str = QString::fromUtf8(m_audioOutput->device.id()).toNSString();
996 player.audioOutputDeviceUniqueID = str;
997 }
998#endif
999}
1000
1002{
1003 if (doLoop()) {
1004 positionChanged(duration());
1005 setPosition(0);
1006 [[m_observer player] setRate:m_rate];
1007 return;
1008 }
1009
1010 //AVPlayerItem has reached end of track/stream
1011#ifdef QT_DEBUG_AVF
1012 qDebug() << Q_FUNC_INFO;
1013#endif
1014 positionChanged(position());
1015
1016 if (m_videoOutput)
1017 m_videoOutput->setLayer(nullptr);
1018
1019 resetBufferProgress();
1020
1021 stateChanged(QMediaPlayer::StoppedState);
1022 mediaStatusChanged(QMediaPlayer::EndOfMedia);
1023}
1024
1025void AVFMediaPlayer::processLoadStateChange(QMediaPlayer::PlaybackState newState)
1026{
1027 AVPlayerStatus currentStatus = [[m_observer player] status];
1028
1029#ifdef QT_DEBUG_AVF
1030 qDebug() << Q_FUNC_INFO << currentStatus << ", " << mediaStatus() << ", " << newState;
1031#endif
1032
1033 if (mediaStatus() == QMediaPlayer::NoMedia)
1034 return;
1035
1036 if (currentStatus == AVPlayerStatusReadyToPlay) {
1037
1038 AVPlayerItem *playerItem = [m_observer playerItem];
1039
1040 applyPitchCompensation(m_pitchCompensationEnabled);
1041
1042 // get the meta data
1043 m_metaData = AVFMetaData::fromAsset(playerItem.asset);
1044 metaDataChanged();
1046
1047 if (playerItem) {
1048 seekableChanged([[playerItem seekableTimeRanges] count] > 0);
1049
1050 // Get the native size of the video, and reset the bounds of the player layer
1051 AVPlayerLayer *playerLayer = [m_observer playerLayer];
1052 if (m_observer.videoTrack && playerLayer) {
1053 if (!playerLayer.bounds.size.width || !playerLayer.bounds.size.height) {
1054 playerLayer.bounds = CGRectMake(0.0f, 0.0f,
1055 m_observer.videoTrack.assetTrack.naturalSize.width,
1056 m_observer.videoTrack.assetTrack.naturalSize.height);
1057 }
1058 }
1059
1060 if (m_requestedPosition != -1)
1061 setPosition(m_requestedPosition);
1062 }
1063
1064 QMediaPlayer::MediaStatus newStatus = (newState != QMediaPlayer::StoppedState)
1065 ? QMediaPlayer::BufferedMedia
1066 : QMediaPlayer::LoadedMedia;
1067
1068 if (newStatus != mediaStatus()) {
1069 if (newStatus == QMediaPlayer::BufferedMedia
1070 && mediaStatus() == QMediaPlayer::LoadingMedia) {
1071 // Emit intermediate transitions to match expected signal sequence
1072 mediaStatusChanged(QMediaPlayer::LoadedMedia);
1073 mediaStatusChanged(QMediaPlayer::BufferingMedia);
1074 } else if (newStatus == QMediaPlayer::BufferedMedia
1075 && mediaStatus() == QMediaPlayer::LoadedMedia) {
1076 mediaStatusChanged(QMediaPlayer::BufferingMedia);
1077 }
1078 mediaStatusChanged(newStatus);
1079 }
1080 }
1081
1082 if (newState == QMediaPlayer::PlayingState && [m_observer player]) {
1083 // Setting the rate is enough to start playback, no need to call play()
1084 [[m_observer player] setRate:m_rate];
1085 m_playbackTimer.start();
1086 }
1087}
1088
1089
1094
1095
1097{
1098 stateChanged(QMediaPlayer::StoppedState);
1099}
1100
1102{
1103 if (state() == QMediaPlayer::StoppedState)
1104 return;
1105
1106 if (bufferProgress == m_bufferProgress)
1107 return;
1108
1109 auto status = mediaStatus();
1110 // Buffered -> unbuffered.
1111 if (!bufferProgress) {
1112 status = QMediaPlayer::StalledMedia;
1113 } else if (status == QMediaPlayer::StalledMedia) {
1114 status = QMediaPlayer::BufferedMedia;
1115 // Resume playback.
1116 if (state() == QMediaPlayer::PlayingState) {
1117 [[m_observer player] setRate:m_rate];
1118 m_playbackTimer.start();
1119 }
1120 }
1121
1122 mediaStatusChanged(status);
1123
1124 m_bufferProgress = bufferProgress;
1125 bufferProgressChanged(bufferProgress / 100.);
1126}
1127
1129{
1130 if (duration == m_duration)
1131 return;
1132
1133 m_duration = duration;
1134 durationChanged(duration);
1135}
1136
1138{
1139 if (state() == QMediaPlayer::StoppedState)
1140 return;
1141
1142 positionChanged(position());
1143}
1144
1145void AVFMediaPlayer::processMediaLoadError(QMediaPlayer::Error errorCode)
1146{
1147 if (m_requestedPosition != -1) {
1148 m_requestedPosition = -1;
1149 positionChanged(position());
1150 }
1151
1152 setInvalidMediaWithError(errorCode, tr("Failed to load media"));
1153}
1154
1156{
1157 m_requestedPosition = -1;
1158}
1159
1164
1166{
1167 QString suffix;
1168 if (!m_resources.isEmpty())
1169 suffix = QFileInfo(m_resources.path()).suffix();
1170 if (suffix.isEmpty() && m_mediaStream)
1171 suffix = QMimeDatabase().mimeTypeForData(m_mediaStream).preferredSuffix();
1172 const QString url = QStringLiteral("iodevice:///iodevice.") + suffix;
1173 setURL(m_observer, QUrl(url), suffix);
1174}
1175
1177{
1178 resetStream(nullptr);
1179}
1180
1182{
1183 bool firstLoad = true;
1184 for (int i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
1185 if (tracks[i].count())
1186 firstLoad = false;
1187 tracks[i].clear();
1188 nativeTracks[i].clear();
1189 }
1190 bool hasAudio = false;
1191 bool hasVideo = false;
1192 AVPlayerItem *playerItem = [m_observer playerItem];
1193 if (playerItem) {
1194 // Check each track for audio and video content
1195 NSArray *tracks = playerItem.tracks;
1196 for (AVPlayerItemTrack *track in tracks) {
1197 AVAssetTrack *assetTrack = track.assetTrack;
1198 if (assetTrack) {
1199 int qtTrack = -1;
1200 if ([assetTrack.mediaType isEqualToString:AVMediaTypeAudio]) {
1201 qtTrack = QPlatformMediaPlayer::AudioStream;
1202 hasAudio = true;
1203 } else if ([assetTrack.mediaType isEqualToString:AVMediaTypeVideo]) {
1204 qtTrack = QPlatformMediaPlayer::VideoStream;
1205 hasVideo = true;
1206 if (m_observer.videoTrack != track) {
1207 m_observer.videoTrack = track;
1208 bool isMirrored = false;
1209 QtVideo::Rotation orientation = QtVideo::Rotation::None;
1210 videoOrientationForAssetTrack(assetTrack, orientation, isMirrored);
1211 orientationChanged(orientation, isMirrored);
1212 }
1213 }
1214 else if ([assetTrack.mediaType isEqualToString:AVMediaTypeSubtitle]) {
1215 qtTrack = QPlatformMediaPlayer::SubtitleStream;
1216 }
1217 if (qtTrack != -1) {
1218 QMediaMetaData metaData = AVFMetaData::fromAssetTrack(assetTrack);
1219 this->tracks[qtTrack].append(metaData);
1220 nativeTracks[qtTrack].append(track);
1221 }
1222 }
1223 }
1224 // subtitles are disabled by default
1225 if (firstLoad)
1226 setActiveTrack(SubtitleStream, -1);
1227 }
1228 audioAvailableChanged(hasAudio);
1229 videoAvailableChanged(hasVideo);
1230 tracksChanged();
1231}
1232
1233void AVFMediaPlayer::setActiveTrack(QPlatformMediaPlayer::TrackType type, int index)
1234{
1235 const auto &t = nativeTracks[type];
1236 if (type == QPlatformMediaPlayer::SubtitleStream) {
1237 // subtitle streams are not always automatically enabled on macOS/iOS.
1238 // this hack ensures they get enables and we actually get the text
1239 AVPlayerItem *playerItem = m_observer.m_playerItem;
1240 if (playerItem) {
1241 AVAsset *asset = playerItem.asset;
1242 if (!asset)
1243 return;
1244 [asset loadMediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible
1245 completionHandler:[=](AVMediaSelectionGroup *group,
1246 NSError *error) {
1247 if (error)
1248 return; // FIXME: handle error
1249 auto *options = group.options;
1250 if (options.count)
1251 [playerItem selectMediaOption:options.firstObject inMediaSelectionGroup:group];
1252 }];
1253 }
1254 }
1255 for (int i = 0; i < t.count(); ++i)
1256 t.at(i).enabled = (i == index);
1257 activeTracksChanged();
1258}
1259
1260int AVFMediaPlayer::activeTrack(QPlatformMediaPlayer::TrackType type)
1261{
1262 const auto &t = nativeTracks[type];
1263 for (int i = 0; i < t.count(); ++i)
1264 if (t.at(i).enabled)
1265 return i;
1266 return -1;
1267}
1268
1269int AVFMediaPlayer::trackCount(QPlatformMediaPlayer::TrackType type)
1270{
1271 return nativeTracks[type].count();
1272}
1273
1274QMediaMetaData AVFMediaPlayer::trackMetaData(QPlatformMediaPlayer::TrackType type, int trackNumber)
1275{
1276 const auto &t = tracks[type];
1277 if (trackNumber < 0 || trackNumber >= t.count())
1278 return QMediaMetaData();
1279 return t.at(trackNumber);
1280}
1281
1282void AVFMediaPlayer::resetStream(QIODevice *stream)
1283{
1284 if (m_mediaStream) {
1285 disconnect(m_mediaStream, &QIODevice::readyRead, this, &AVFMediaPlayer::streamReady);
1286 disconnect(m_mediaStream, &QIODevice::destroyed, this, &AVFMediaPlayer::streamDestroyed);
1287 }
1288
1289 m_mediaStream = stream;
1290
1291 if (m_mediaStream) {
1292 connect(m_mediaStream, &QIODevice::readyRead, this, &AVFMediaPlayer::streamReady);
1293 connect(m_mediaStream, &QIODevice::destroyed, this, &AVFMediaPlayer::streamDestroyed);
1294 }
1295}
1296
1297void AVFMediaPlayer::applyPitchCompensation(bool enabled)
1298{
1299 AVPlayerItem *playerItem = [m_observer playerItem];
1300 if (playerItem) {
1301 if (enabled)
1302 playerItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmSpectral;
1303 else
1304 playerItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmVarispeed;
1305 }
1306}
1307
1308void AVFMediaPlayer::resetBufferProgress()
1309{
1310 if (m_bufferProgress != 0) {
1311 m_bufferProgress = 0;
1312 bufferProgressChanged(0);
1313 }
1314}
1315
1317{
1318 if (!m_videoSink)
1319 return;
1320 m_videoSink->setNativeSize(size);
1321}
1322
1323void AVFMediaPlayer::orientationChanged(QtVideo::Rotation rotation, bool mirrored)
1324{
1325 if (!m_videoOutput)
1326 return;
1327
1328 m_videoOutput->setVideoRotation(rotation);
1329 m_videoOutput->setVideoMirrored(mirrored);
1330}
1331
1332void AVFMediaPlayer::videoOrientationForAssetTrack(AVAssetTrack *videoTrack,
1333 QtVideo::Rotation &angle,
1334 bool &mirrored)
1335{
1336 angle = QtVideo::Rotation::None;
1337 mirrored = false;
1338 if (videoTrack) {
1339 CGAffineTransform transform = videoTrack.preferredTransform;
1340 if (CGAffineTransformIsIdentity(transform))
1341 return;
1342
1343 // determinant < 0 means the transform includes a reflection (mirror)
1344 qreal det = transform.a * transform.d - transform.b * transform.c;
1345 mirrored = (det < 0.0);
1346
1347 // Factor out mirror before computing rotation angle.
1348 // Negating the first column of a mirrored matrix yields a pure rotation.
1349 qreal ra = mirrored ? -transform.a : transform.a;
1350 qreal rb = mirrored ? -transform.b : transform.b;
1351
1352 qreal degrees = qRadiansToDegrees(qAtan2(rb, ra));
1353 if (degrees < 0)
1354 degrees += 360.0;
1355
1356 if (QtPrivate::fuzzyCompare(degrees, qreal(90))
1357 || QtPrivate::fuzzyCompare(degrees, qreal(-270))) {
1358 angle = QtVideo::Rotation::Clockwise90;
1359 } else if (QtPrivate::fuzzyCompare(degrees, qreal(-90))
1360 || QtPrivate::fuzzyCompare(degrees, qreal(270))) {
1361 angle = QtVideo::Rotation::Clockwise270;
1362 } else if (QtPrivate::fuzzyCompare(degrees, qreal(180))
1363 || QtPrivate::fuzzyCompare(degrees, qreal(-180))) {
1364 angle = QtVideo::Rotation::Clockwise180;
1365 }
1366 }
1367}
1368
1370{
1371 if (m_pitchCompensationEnabled == enabled)
1372 return;
1373
1374 applyPitchCompensation(enabled);
1375
1376 m_pitchCompensationEnabled = enabled;
1377 pitchCompensationChanged(enabled);
1378}
1379
1381{
1382 return m_pitchCompensationEnabled;
1383}
1384
1387{
1388 return QPlatformMediaPlayer::PitchCompensationAvailability::Available;
1389}
1390
1391#include "moc_avfmediaplayer_p.cpp"
static void * AVFMediaPlayerObserverCurrentItemObservationContext
static NSString *const AVF_BUFFER_LIKELY_KEEP_UP_KEY
static void * AVFMediaPlayerObserverPresentationSizeContext
static void * AVFMediaPlayerObserverTracksContext
static QT_USE_NAMESPACE NSString *const AVF_TRACKS_KEY
static NSString *const AVF_STATUS_KEY
static NSString *const AVF_CURRENT_ITEM_DURATION_KEY
static void * AVFMediaPlayerObserverRateObservationContext
static void setURL(AVFMediaPlayerObserver *observer, const QUrl &url, const QString &mimeType=QString())
static NSString *const AVF_CURRENT_ITEM_KEY
static NSString *const AVF_PLAYABLE_KEY
static void * AVFMediaPlayerObserverBufferLikelyToKeepUpContext
static NSString *const AVF_RATE_KEY
static void * AVFMediaPlayerObserverCurrentItemDurationObservationContext
static void * AVFMediaPlayerObserverStatusObservationContext
QMediaMetaData trackMetaData(TrackType type, int trackNumber) override
qint64 duration() const override
void setVolume(float volume)
void processLoadStateChange()
int trackCount(TrackType) override
void setPosition(qint64 pos) override
void setVideoSink(QVideoSink *sink) override
QMediaTimeRange availablePlaybackRanges() const override
void nativeSizeChanged(QSize size)
QMediaMetaData metaData() const override
void setVideoOutput(AVFVideoRendererControl *output)
void stop() override
float bufferProgress() const override
void processMediaLoadError(QMediaPlayer::Error errorCode)
void setMedia(const QUrl &content, QIODevice *stream) override
qint64 position() const override
int activeTrack(QPlatformMediaPlayer::TrackType type) override
void setPitchCompensation(bool enabled) override
void setMuted(bool muted)
void pause() override
void play() override
void processDurationChange(qint64 duration)
QUrl media() const override
bool pitchCompensation() const override
void processLoadStateFailure()
void updateAudioOutputDevice()
qreal playbackRate() const override
void processLoadStateChange(QMediaPlayer::PlaybackState newState)
QIODevice * mediaStream() const override
PitchCompensationAvailability pitchCompensationAvailability() const override
~AVFMediaPlayer() override
AVAsset * currentAssetHandle()
void setAudioOutput(QPlatformAudioOutput *output) override
void setActiveTrack(QPlatformMediaPlayer::TrackType type, int index) override
void processBufferStateChange(int bufferProgress)
void setVideoSink(AVFVideoSink *sink)