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