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