Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
4#include "avfmediaplayer_p.h"
6#include <avfvideosink_p.h>
7#include <avfmetadata_p.h>
8
9#include "qaudiooutput.h"
10#include "private/qplatformaudiooutput_p.h"
11
12#include <qpointer.h>
13#include <QFileInfo>
14#include <QtCore/qmath.h>
15#include <QtCore/qmutex.h>
16
17#import <AVFoundation/AVFoundation.h>
18
20
21//AVAsset Keys
22static NSString* const AVF_TRACKS_KEY = @"tracks";
23static NSString* const AVF_PLAYABLE_KEY = @"playable";
24
25//AVPlayerItem keys
26static NSString* const AVF_STATUS_KEY = @"status";
27static NSString* const AVF_BUFFER_LIKELY_KEEP_UP_KEY = @"playbackLikelyToKeepUp";
28
29//AVPlayer keys
30static NSString* const AVF_RATE_KEY = @"rate";
31static NSString* const AVF_CURRENT_ITEM_KEY = @"currentItem";
32static NSString* const AVF_CURRENT_ITEM_DURATION_KEY = @"currentItem.duration";
33
41
42@interface AVFMediaPlayerObserver : NSObject<AVAssetResourceLoaderDelegate>
43
44@property (readonly, getter=player) AVPlayer* m_player;
45@property (readonly, getter=playerItem) AVPlayerItem* m_playerItem;
46@property (readonly, getter=playerLayer) AVPlayerLayer* m_playerLayer;
47@property (readonly, getter=session) AVFMediaPlayer* m_session;
48@property (retain) AVPlayerItemTrack *videoTrack;
49
50- (AVFMediaPlayerObserver *) initWithMediaPlayerSession:(AVFMediaPlayer *)session;
51- (void) setURL:(NSURL *)url mimeType:(NSString *)mimeType;
53- (void) prepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys;
54- (void) assetFailedToPrepareForPlayback:(NSError *)error;
55- (void) playerItemDidReachEnd:(NSNotification *)notification;
56- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
57 change:(NSDictionary *)change context:(void *)context;
59- (void) dealloc;
60- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest;
61@end
62
63#ifdef Q_OS_IOS
64// Alas, no such thing as 'class variable', hence globals:
65static unsigned sessionActivationCount;
66static QMutex sessionMutex;
67#endif // Q_OS_IOS
68
69@implementation AVFMediaPlayerObserver
70{
71@private
73 AVPlayer *m_player;
74 AVPlayerItem *m_playerItem;
75 AVPlayerLayer *m_playerLayer;
76 NSURL *m_URL;
78 NSData *m_data;
79 NSString *m_mimeType;
80#ifdef Q_OS_IOS
81 BOOL m_activated;
82#endif
83}
84
86
87#ifdef Q_OS_IOS
88- (void)setSessionActive:(BOOL)active
89{
90 const QMutexLocker lock(&sessionMutex);
91 if (active) {
92 // Don't count the same player twice if already activated,
93 // unless it tried to deactivate first:
94 if (m_activated)
95 return;
96 if (!sessionActivationCount)
97 [AVAudioSession.sharedInstance setActive:YES error:nil];
98 ++sessionActivationCount;
99 m_activated = YES;
100 } else {
101 if (!sessionActivationCount || !m_activated) {
102 qWarning("Unbalanced audio session deactivation, ignoring.");
103 return;
104 }
105 --sessionActivationCount;
106 m_activated = NO;
107 if (!sessionActivationCount)
108 [AVAudioSession.sharedInstance setActive:NO error:nil];
109 }
110}
111#endif // Q_OS_IOS
112
113- (AVFMediaPlayerObserver *) initWithMediaPlayerSession:(AVFMediaPlayer *)session
114{
115 if (!(self = [super init]))
116 return nil;
117
118 m_session = session;
120
121 m_playerLayer = [AVPlayerLayer playerLayerWithPlayer:nil];
122 [m_playerLayer retain];
123 m_playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
124 m_playerLayer.anchorPoint = CGPointMake(0.0f, 0.0f);
125 return self;
126}
127
128- (void) setURL:(NSURL *)url mimeType:(NSString *)mimeType
129{
130 if (!m_session)
131 return;
132
133 [m_mimeType release];
134 m_mimeType = [mimeType retain];
135
136 if (m_URL != url)
137 {
138 [m_URL release];
139 m_URL = [url copy];
140
141 //Create an asset for inspection of a resource referenced by a given URL.
142 //Load the values for the asset keys "tracks", "playable".
143
144 // use __block to avoid maintaining strong references on variables captured by the
145 // following block callback
146#if defined(Q_OS_IOS)
147 BOOL isAccessing = [m_URL startAccessingSecurityScopedResource];
148#endif
149 __block AVURLAsset *asset = [[AVURLAsset URLAssetWithURL:m_URL options:nil] retain];
150 [asset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()];
151
152 __block NSArray *requestedKeys = [[NSArray arrayWithObjects:AVF_TRACKS_KEY, AVF_PLAYABLE_KEY, nil] retain];
153
154 __block AVFMediaPlayerObserver *blockSelf = [self retain];
155
156 // Tells the asset to load the values of any of the specified keys that are not already loaded.
157 [asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:
158 ^{
159 dispatch_async( dispatch_get_main_queue(),
160 ^{
161#if defined(Q_OS_IOS)
162 if (isAccessing)
163 [m_URL stopAccessingSecurityScopedResource];
164#endif
165 [blockSelf prepareToPlayAsset:asset withKeys:requestedKeys];
166 [asset release];
167 [requestedKeys release];
168 [blockSelf release];
169 });
170 }];
171 }
172}
173
175{
176 if (m_playerItem) {
177 [m_playerItem removeObserver:self forKeyPath:@"presentationSize"];
178 [m_playerItem removeObserver:self forKeyPath:AVF_STATUS_KEY];
179 [m_playerItem removeObserver:self forKeyPath:AVF_BUFFER_LIKELY_KEEP_UP_KEY];
180 [m_playerItem removeObserver:self forKeyPath:AVF_TRACKS_KEY];
181
182 [[NSNotificationCenter defaultCenter] removeObserver:self
183 name:AVPlayerItemDidPlayToEndTimeNotification
184 object:m_playerItem];
185 m_playerItem = 0;
186 }
187 if (m_player) {
188 [m_player setRate:0.0];
189 [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_DURATION_KEY];
190 [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_KEY];
191 [m_player removeObserver:self forKeyPath:AVF_RATE_KEY];
192 [m_player release];
193 m_player = 0;
194 }
195 if (m_playerLayer)
196 m_playerLayer.player = nil;
197#if defined(Q_OS_IOS)
198 [self setSessionActive:NO];
199#endif
200}
201
202- (void) prepareToPlayAsset:(AVURLAsset *)asset
203 withKeys:(NSArray *)requestedKeys
204{
205 if (!m_session)
206 return;
207
208 //Make sure that the value of each key has loaded successfully.
209 for (NSString *thisKey in requestedKeys)
210 {
211 NSError *error = nil;
212 AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
213#ifdef QT_DEBUG_AVF
214 qDebug() << Q_FUNC_INFO << [thisKey UTF8String] << " status: " << keyStatus;
215#endif
216 if (keyStatus == AVKeyValueStatusFailed)
217 {
218 [self assetFailedToPrepareForPlayback:error];
219 return;
220 }
221 }
222
223 //Use the AVAsset playable property to detect whether the asset can be played.
224#ifdef QT_DEBUG_AVF
225 qDebug() << Q_FUNC_INFO << "isPlayable: " << [asset isPlayable];
226#endif
227 if (!asset.playable)
228 qWarning() << "Asset reported to be not playable. Playback of this asset may not be possible.";
229
230 //At this point we're ready to set up for playback of the asset.
231 //Stop observing our prior AVPlayerItem, if we have one.
232 if (m_playerItem)
233 {
234 //Remove existing player item key value observers and notifications.
235 [self unloadMedia];
236 }
237
238 //Create a new instance of AVPlayerItem from the now successfully loaded AVAsset.
239 m_playerItem = [AVPlayerItem playerItemWithAsset:asset];
240 if (!m_playerItem) {
241 qWarning() << "Failed to create player item";
242 //Generate an error describing the failure.
243 NSString *localizedDescription = NSLocalizedString(@"Item cannot be played", @"Item cannot be played description");
244 NSString *localizedFailureReason = NSLocalizedString(@"The assets tracks were loaded, but couldn't create player item.", @"Item cannot be played failure reason");
245 NSDictionary *errorDict = [NSDictionary dictionaryWithObjectsAndKeys:
246 localizedDescription, NSLocalizedDescriptionKey,
247 localizedFailureReason, NSLocalizedFailureReasonErrorKey,
248 nil];
249 NSError *assetCannotBePlayedError = [NSError errorWithDomain:@"StitchedStreamPlayer" code:0 userInfo:errorDict];
250
251 [self assetFailedToPrepareForPlayback:assetCannotBePlayedError];
252 return;
253 }
254
255 //Observe the player item "status" key to determine when it is ready to play.
256 [m_playerItem addObserver:self
257 forKeyPath:AVF_STATUS_KEY
258 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
259 context:AVFMediaPlayerObserverStatusObservationContext];
260
261 [m_playerItem addObserver:self
262 forKeyPath:@"presentationSize"
263 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
264 context:AVFMediaPlayerObserverPresentationSizeContext];
265
266 [m_playerItem addObserver:self
267 forKeyPath:AVF_BUFFER_LIKELY_KEEP_UP_KEY
268 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
269 context:AVFMediaPlayerObserverBufferLikelyToKeepUpContext];
270
271 [m_playerItem addObserver:self
272 forKeyPath:AVF_TRACKS_KEY
273 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
274 context:AVFMediaPlayerObserverTracksContext];
275
276 //When the player item has played to its end time we'll toggle
277 //the movie controller Pause button to be the Play button
278 [[NSNotificationCenter defaultCenter] addObserver:self
279 selector:@selector(playerItemDidReachEnd:)
280 name:AVPlayerItemDidPlayToEndTimeNotification
281 object:m_playerItem];
282
283 //Get a new AVPlayer initialized to play the specified player item.
284 m_player = [AVPlayer playerWithPlayerItem:m_playerItem];
285 [m_player retain];
286
287 //Set the initial volume on new player object
288 if (self.session) {
289 auto *audioOutput = m_session->m_audioOutput;
290 m_player.volume = (audioOutput ? audioOutput->volume : 1.);
291 m_player.muted = (audioOutput ? audioOutput->muted : true);
292 }
293
294 //Assign the output layer to the new player
295 m_playerLayer.player = m_player;
296
297 //Observe the AVPlayer "currentItem" property to find out when any
298 //AVPlayer replaceCurrentItemWithPlayerItem: replacement will/did
299 //occur.
300 [m_player addObserver:self
301 forKeyPath:AVF_CURRENT_ITEM_KEY
302 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
303 context:AVFMediaPlayerObserverCurrentItemObservationContext];
304
305 //Observe the AVPlayer "rate" property to update the scrubber control.
306 [m_player addObserver:self
307 forKeyPath:AVF_RATE_KEY
308 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
309 context:AVFMediaPlayerObserverRateObservationContext];
310
311 //Observe the duration for getting the buffer state
312 [m_player addObserver:self
313 forKeyPath:AVF_CURRENT_ITEM_DURATION_KEY
314 options:0
315 context:AVFMediaPlayerObserverCurrentItemDurationObservationContext];
316#if defined(Q_OS_IOS)
317 [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
318 [self setSessionActive:YES];
319#endif
320}
321
322-(void) assetFailedToPrepareForPlayback:(NSError *)error
323{
325 QMetaObject::invokeMethod(m_session, "processMediaLoadError", Qt::AutoConnection);
326#ifdef QT_DEBUG_AVF
327 qDebug() << Q_FUNC_INFO;
328 qDebug() << [[error localizedDescription] UTF8String];
329 qDebug() << [[error localizedFailureReason] UTF8String];
330 qDebug() << [[error localizedRecoverySuggestion] UTF8String];
331#endif
332}
333
334- (void) playerItemDidReachEnd:(NSNotification *)notification
335{
336 Q_UNUSED(notification);
337 if (self.session)
339}
340
341- (void) observeValueForKeyPath:(NSString*) path
342 ofObject:(id)object
343 change:(NSDictionary*)change
344 context:(void*)context
345{
346 //AVPlayerItem "status" property value observer.
348 {
349 AVPlayerStatus status = (AVPlayerStatus)[[change objectForKey:NSKeyValueChangeNewKey] integerValue];
350 switch (status)
351 {
352 //Indicates that the status of the player is not yet known because
353 //it has not tried to load new media resources for playback
354 case AVPlayerStatusUnknown:
355 {
356 //QMetaObject::invokeMethod(m_session, "processLoadStateChange", Qt::AutoConnection);
357 }
358 break;
359
360 case AVPlayerStatusReadyToPlay:
361 {
362 //Once the AVPlayerItem becomes ready to play, i.e.
363 //[playerItem status] == AVPlayerItemStatusReadyToPlay,
364 //its duration can be fetched from the item.
365 if (self.session)
366 QMetaObject::invokeMethod(m_session, "processLoadStateChange", Qt::AutoConnection);
367 }
368 break;
369
370 case AVPlayerStatusFailed:
371 {
372 AVPlayerItem *playerItem = static_cast<AVPlayerItem*>(object);
373 [self assetFailedToPrepareForPlayback:playerItem.error];
374
375 if (self.session)
376 QMetaObject::invokeMethod(m_session, "processLoadStateFailure", Qt::AutoConnection);
377 }
378 break;
379 }
381 QSize size(m_playerItem.presentationSize.width, m_playerItem.presentationSize.height);
384 {
385 const bool isPlaybackLikelyToKeepUp = [m_playerItem isPlaybackLikelyToKeepUp];
386 if (isPlaybackLikelyToKeepUp != m_bufferIsLikelyToKeepUp) {
387 m_bufferIsLikelyToKeepUp = isPlaybackLikelyToKeepUp;
388 QMetaObject::invokeMethod(m_session, "processBufferStateChange", Qt::AutoConnection,
389 Q_ARG(int, isPlaybackLikelyToKeepUp ? 100 : 0));
390 }
391 }
393 {
395 }
396 //AVPlayer "rate" property value observer.
398 {
399 //QMetaObject::invokeMethod(m_session, "setPlaybackRate", Qt::AutoConnection, Q_ARG(qreal, [m_player rate]));
400 }
401 //AVPlayer "currentItem" property observer.
402 //Called when the AVPlayer replaceCurrentItemWithPlayerItem:
403 //replacement will/did occur.
405 {
406 AVPlayerItem *newPlayerItem = [change objectForKey:NSKeyValueChangeNewKey];
407 if (m_playerItem != newPlayerItem)
408 m_playerItem = newPlayerItem;
409 }
411 {
412 const CMTime time = [m_playerItem duration];
413 const qint64 duration = static_cast<qint64>(float(time.value) / float(time.timescale) * 1000.0f);
414 if (self.session)
415 QMetaObject::invokeMethod(m_session, "processDurationChange", Qt::AutoConnection, Q_ARG(qint64, duration));
416 }
417 else
418 {
419 [super observeValueForKeyPath:path ofObject:object change:change context:context];
420 }
421}
422
424{
425#ifdef QT_DEBUG_AVF
426 qDebug() << Q_FUNC_INFO;
427#endif
428 m_session = 0;
429}
430
431- (void) dealloc
432{
433#ifdef QT_DEBUG_AVF
434 qDebug() << Q_FUNC_INFO;
435#endif
436 [self unloadMedia];
437
438 if (m_URL) {
439 [m_URL release];
440 }
441
442 [m_mimeType release];
443 [m_playerLayer release];
444 [super dealloc];
445}
446
447- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
448{
449 Q_UNUSED(resourceLoader);
450
451 if (![loadingRequest.request.URL.scheme isEqualToString:@"iodevice"])
452 return NO;
453
455 if (!device)
456 return NO;
457
458 device->seek(loadingRequest.dataRequest.requestedOffset);
459 if (loadingRequest.contentInformationRequest) {
460 loadingRequest.contentInformationRequest.contentType = m_mimeType;
461 loadingRequest.contentInformationRequest.contentLength = device->size();
462 loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;
463 }
464
465 if (loadingRequest.dataRequest) {
466 NSInteger requestedLength = loadingRequest.dataRequest.requestedLength;
467 int maxBytes = qMin(32 * 1064, int(requestedLength));
468 char buffer[maxBytes];
469 NSInteger submitted = 0;
470 while (submitted < requestedLength) {
471 qint64 len = device->read(buffer, maxBytes);
472 if (len < 1)
473 break;
474
475 [loadingRequest.dataRequest respondWithData:[NSData dataWithBytes:buffer length:len]];
476 submitted += len;
477 }
478
479 // Finish loading even if not all bytes submitted.
480 [loadingRequest finishLoading];
481 }
482
483 return YES;
484}
485@end
486
488 : QObject(player),
490 m_state(QMediaPlayer::StoppedState),
491 m_mediaStatus(QMediaPlayer::NoMedia),
492 m_mediaStream(nullptr),
493 m_rate(1.0),
494 m_requestedPosition(-1),
495 m_duration(0),
496 m_bufferProgress(0),
497 m_videoAvailable(false),
498 m_audioAvailable(false),
499 m_seekable(false)
500{
501 m_observer = [[AVFMediaPlayerObserver alloc] initWithMediaPlayerSession:this];
504}
505
507{
508#ifdef QT_DEBUG_AVF
509 qDebug() << Q_FUNC_INFO;
510#endif
511 //Detatch the session from the sessionObserver (which could still be alive trying to communicate with this session).
513 [static_cast<AVFMediaPlayerObserver*>(m_observer) release];
514}
515
517{
518 m_videoSink = sink ? static_cast<AVFVideoSink *>(sink->platformVideoSink()): nullptr;
519 m_videoOutput->setVideoSink(m_videoSink);
520}
521
523{
524#ifdef QT_DEBUG_AVF
525 qDebug() << Q_FUNC_INFO << output;
526#endif
527
528 if (m_videoOutput == output)
529 return;
530
531 //Set the current output layer to null to stop rendering
532 if (m_videoOutput) {
533 m_videoOutput->setLayer(nullptr);
534 }
535
536 m_videoOutput = output;
537
538 if (m_videoOutput && m_state != QMediaPlayer::StoppedState)
539 m_videoOutput->setLayer([static_cast<AVFMediaPlayerObserver*>(m_observer) playerLayer]);
540}
541
543{
544#ifdef QT_DEBUG_AVF
545 qDebug() << Q_FUNC_INFO;
546#endif
547 AVAsset *currentAsset = [[static_cast<AVFMediaPlayerObserver*>(m_observer) playerItem] asset];
548 return currentAsset;
549}
550
552{
553 return m_state;
554}
555
557{
558 return m_mediaStatus;
559}
560
562{
563 return m_resources;
564}
565
567{
568 return m_mediaStream;
569}
570
571static void setURL(AVFMediaPlayerObserver *observer, const QByteArray &url, const QString &mimeType = QString())
572{
573 NSString *urlString = [NSString stringWithUTF8String:url.constData()];
574 NSURL *nsurl = [NSURL URLWithString:urlString];
575 [observer setURL:nsurl mimeType:[NSString stringWithUTF8String:mimeType.toLatin1().constData()]];
576}
577
578static void setStreamURL(AVFMediaPlayerObserver *observer, const QByteArray &url)
579{
580 setURL(observer, QByteArrayLiteral("iodevice://") + url, QFileInfo(QString::fromUtf8(url)).suffix());
581}
582
584{
585#ifdef QT_DEBUG_AVF
586 qDebug() << Q_FUNC_INFO << content.request().url();
587#endif
588
589 [static_cast<AVFMediaPlayerObserver*>(m_observer) unloadMedia];
590
591 m_resources = content;
592 resetStream(stream);
593
594 setAudioAvailable(false);
595 setVideoAvailable(false);
596 setSeekable(false);
597 m_requestedPosition = -1;
598 orientationChanged(QtVideo::Rotation::None, false);
600 if (m_duration != 0) {
601 m_duration = 0;
603 }
604 if (!m_metaData.isEmpty()) {
605 m_metaData.clear();
607 }
608 for (int i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
609 tracks[i].clear();
610 nativeTracks[i].clear();
611 }
613
614 const QMediaPlayer::MediaStatus oldMediaStatus = m_mediaStatus;
615 const QMediaPlayer::PlaybackState oldState = m_state;
616
617 if (!m_mediaStream && content.isEmpty()) {
618 m_mediaStatus = QMediaPlayer::NoMedia;
619 if (m_mediaStatus != oldMediaStatus)
620 Q_EMIT mediaStatusChanged(m_mediaStatus);
621
623 if (m_state != oldState)
624 Q_EMIT stateChanged(m_state);
625
626 return;
627 }
628
629 m_mediaStatus = QMediaPlayer::LoadingMedia;
630 if (m_mediaStatus != oldMediaStatus)
631 Q_EMIT mediaStatusChanged(m_mediaStatus);
632
633 if (m_mediaStream) {
634 // If there is a data, try to load it,
635 // otherwise wait for readyRead.
636 if (m_mediaStream->size())
637 setStreamURL(m_observer, m_resources.toEncoded());
638 } else {
639 //Load AVURLAsset
640 //initialize asset using content's URL
641 setURL(m_observer, m_resources.toEncoded());
642 }
643
645 if (m_state != oldState)
646 Q_EMIT stateChanged(m_state);
647}
648
650{
651 AVPlayerItem *playerItem = [static_cast<AVFMediaPlayerObserver*>(m_observer) playerItem];
652
653 if (m_requestedPosition != -1)
654 return m_requestedPosition;
655
656 if (!playerItem)
657 return 0;
658
659 CMTime time = [playerItem currentTime];
660 return static_cast<quint64>(float(time.value) / float(time.timescale) * 1000.0f);
661}
662
664{
665#ifdef QT_DEBUG_AVF
666 qDebug() << Q_FUNC_INFO;
667#endif
668 return m_duration;
669}
670
672{
673#ifdef QT_DEBUG_AVF
674 qDebug() << Q_FUNC_INFO;
675#endif
676 return m_bufferProgress/100.;
677}
678
679void AVFMediaPlayer::setAudioAvailable(bool available)
680{
681 if (m_audioAvailable == available)
682 return;
683
684 m_audioAvailable = available;
685 Q_EMIT audioAvailableChanged(available);
686}
687
689{
690 return m_audioAvailable;
691}
692
693void AVFMediaPlayer::setVideoAvailable(bool available)
694{
695 if (m_videoAvailable == available)
696 return;
697
698 m_videoAvailable = available;
699 Q_EMIT videoAvailableChanged(available);
700}
701
703{
704 return m_videoAvailable;
705}
706
708{
709 return m_seekable;
710}
711
712void AVFMediaPlayer::setSeekable(bool seekable)
713{
714 if (m_seekable == seekable)
715 return;
716
717 m_seekable = seekable;
718 Q_EMIT seekableChanged(seekable);
719}
720
722{
723 AVPlayerItem *playerItem = [static_cast<AVFMediaPlayerObserver*>(m_observer) playerItem];
724
725 if (playerItem) {
726 QMediaTimeRange timeRanges;
727
728 NSArray *ranges = [playerItem loadedTimeRanges];
729 for (NSValue *timeRange in ranges) {
730 CMTimeRange currentTimeRange = [timeRange CMTimeRangeValue];
731 qint64 startTime = qint64(float(currentTimeRange.start.value) / currentTimeRange.start.timescale * 1000.0);
732 timeRanges.addInterval(startTime, startTime + qint64(float(currentTimeRange.duration.value) / currentTimeRange.duration.timescale * 1000.0));
733 }
734 if (!timeRanges.isEmpty())
735 return timeRanges;
736 }
737 return QMediaTimeRange(0, duration());
738}
739
741{
742 return m_rate;
743}
744
762
764{
765 return m_metaData;
766}
767
769{
770#ifdef QT_DEBUG_AVF
771 qDebug() << Q_FUNC_INFO << rate;
772#endif
773
774 if (qFuzzyCompare(m_rate, rate))
775 return;
776
777 m_rate = rate;
778
779 AVPlayer *player = [static_cast<AVFMediaPlayerObserver*>(m_observer) player];
780 if (player && m_state == QMediaPlayer::PlayingState)
781 [player setRate:m_rate];
782
784}
785
787{
788#ifdef QT_DEBUG_AVF
789 qDebug() << Q_FUNC_INFO << pos;
790#endif
791
792 if (pos == position())
793 return;
794
795 AVPlayerItem *playerItem = [static_cast<AVFMediaPlayerObserver*>(m_observer) playerItem];
796 if (!playerItem) {
797 m_requestedPosition = pos;
798 Q_EMIT positionChanged(m_requestedPosition);
799 return;
800 }
801
802 if (!isSeekable()) {
803 if (m_requestedPosition != -1) {
804 m_requestedPosition = -1;
806 }
807 return;
808 }
809
810 pos = qMax(qint64(0), pos);
811 if (duration() > 0)
812 pos = qMin(pos, duration());
813 m_requestedPosition = pos;
814
815 CMTime newTime = [playerItem currentTime];
816 newTime.value = (pos / 1000.0f) * newTime.timescale;
817 [playerItem seekToTime:newTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero
818 completionHandler:^(BOOL finished) {
819 if (finished)
820 m_requestedPosition = -1;
821 }];
822
824
825 // Reset media status if the current status is EndOfMedia
826 if (m_mediaStatus == QMediaPlayer::EndOfMedia) {
829 Q_EMIT mediaStatusChanged((m_mediaStatus = newMediaStatus));
830 }
831}
832
834{
835#ifdef QT_DEBUG_AVF
836 qDebug() << Q_FUNC_INFO << "currently: " << m_state;
837#endif
838
839 if (m_mediaStatus == QMediaPlayer::NoMedia || m_mediaStatus == QMediaPlayer::InvalidMedia)
840 return;
841
842 if (m_state == QMediaPlayer::PlayingState)
843 return;
844
846
847 if (m_videoOutput && m_videoSink)
848 m_videoOutput->setLayer([static_cast<AVFMediaPlayerObserver*>(m_observer) playerLayer]);
849
850 // Reset media status if the current status is EndOfMedia
851 if (m_mediaStatus == QMediaPlayer::EndOfMedia)
852 setPosition(0);
853
854 if (m_mediaStatus == QMediaPlayer::LoadedMedia || m_mediaStatus == QMediaPlayer::BufferedMedia) {
855 // Setting the rate starts playback
856 [[static_cast<AVFMediaPlayerObserver*>(m_observer) player] setRate:m_rate];
857 }
858
861
862 Q_EMIT stateChanged(m_state);
863 m_playbackTimer.start(100);
864}
865
867{
868#ifdef QT_DEBUG_AVF
869 qDebug() << Q_FUNC_INFO << "currently: " << m_state;
870#endif
871
872 if (m_mediaStatus == QMediaPlayer::NoMedia)
873 return;
874
875 if (m_state == QMediaPlayer::PausedState)
876 return;
877
879
880 if (m_videoOutput && m_videoSink)
881 m_videoOutput->setLayer([static_cast<AVFMediaPlayerObserver*>(m_observer) playerLayer]);
882
883 [[static_cast<AVFMediaPlayerObserver*>(m_observer) player] pause];
884
885 // Reset media status if the current status is EndOfMedia
886 if (m_mediaStatus == QMediaPlayer::EndOfMedia)
887 setPosition(0);
888
890 Q_EMIT stateChanged(m_state);
891 m_playbackTimer.stop();
892}
893
895{
896#ifdef QT_DEBUG_AVF
897 qDebug() << Q_FUNC_INFO << "currently: " << m_state;
898#endif
899
900 if (m_state == QMediaPlayer::StoppedState)
901 return;
902
903 // AVPlayer doesn't have stop(), only pause() and play().
904 [[static_cast<AVFMediaPlayerObserver*>(m_observer) player] pause];
905 setPosition(0);
906
907 if (m_videoOutput)
908 m_videoOutput->setLayer(nullptr);
909
910 if (m_mediaStatus == QMediaPlayer::BufferedMedia)
912
914 m_playbackTimer.stop();
915}
916
918{
919#ifdef QT_DEBUG_AVF
920 qDebug() << Q_FUNC_INFO << volume;
921#endif
922
923 AVPlayer *player = [static_cast<AVFMediaPlayerObserver*>(m_observer) player];
924 if (player)
925 player.volume = volume;
926}
927
929{
930#ifdef QT_DEBUG_AVF
931 qDebug() << Q_FUNC_INFO << muted;
932#endif
933
934 AVPlayer *player = [static_cast<AVFMediaPlayerObserver*>(m_observer) player];
935 if (player)
936 player.muted = muted;
937}
938
940{
941#ifdef Q_OS_MACOS
942 AVPlayer *player = [static_cast<AVFMediaPlayerObserver*>(m_observer) player];
944 player.audioOutputDeviceUniqueID = nil;
945 if (!m_audioOutput)
946 player.muted = true;
947 } else {
948 NSString *str = QString::fromUtf8(m_audioOutput->device.id()).toNSString();
949 player.audioOutputDeviceUniqueID = str;
950 }
951#endif
952}
953
955{
956 if (doLoop()) {
957 setPosition(0);
958 [[static_cast<AVFMediaPlayerObserver*>(m_observer) player] setRate:m_rate];
959 return;
960 }
961
962 //AVPlayerItem has reached end of track/stream
963#ifdef QT_DEBUG_AVF
964 qDebug() << Q_FUNC_INFO;
965#endif
967 m_mediaStatus = QMediaPlayer::EndOfMedia;
969
970 if (m_videoOutput)
971 m_videoOutput->setLayer(nullptr);
972
973 Q_EMIT mediaStatusChanged(m_mediaStatus);
974 Q_EMIT stateChanged(m_state);
975}
976
978{
979 AVPlayerStatus currentStatus = [[static_cast<AVFMediaPlayerObserver*>(m_observer) player] status];
980
981#ifdef QT_DEBUG_AVF
982 qDebug() << Q_FUNC_INFO << currentStatus << ", " << m_mediaStatus << ", " << newState;
983#endif
984
985 if (m_mediaStatus == QMediaPlayer::NoMedia)
986 return;
987
988 if (currentStatus == AVPlayerStatusReadyToPlay) {
989
990 QMediaPlayer::MediaStatus newStatus = m_mediaStatus;
991
992 AVPlayerItem *playerItem = [m_observer playerItem];
993
994 // get the meta data
995 m_metaData = AVFMetaData::fromAsset(playerItem.asset);
997 updateTracks();
998
999 if (playerItem) {
1000 setSeekable([[playerItem seekableTimeRanges] count] > 0);
1001
1002 // Get the native size of the video, and reset the bounds of the player layer
1003 AVPlayerLayer *playerLayer = [m_observer playerLayer];
1004 if (m_observer.videoTrack && playerLayer) {
1005 if (!playerLayer.bounds.size.width || !playerLayer.bounds.size.height) {
1006 playerLayer.bounds = CGRectMake(0.0f, 0.0f,
1007 m_observer.videoTrack.assetTrack.naturalSize.width,
1008 m_observer.videoTrack.assetTrack.naturalSize.height);
1009 }
1010 }
1011
1012 if (m_requestedPosition != -1) {
1013 setPosition(m_requestedPosition);
1014 m_requestedPosition = -1;
1015 }
1016 }
1017
1020
1021 if (newStatus != m_mediaStatus)
1022 Q_EMIT mediaStatusChanged((m_mediaStatus = newStatus));
1023
1024 }
1025
1026 if (newState == QMediaPlayer::PlayingState && [static_cast<AVFMediaPlayerObserver*>(m_observer) player]) {
1027 // Setting the rate is enough to start playback, no need to call play()
1028 [[static_cast<AVFMediaPlayerObserver*>(m_observer) player] setRate:m_rate];
1029 m_playbackTimer.start();
1030 }
1031}
1032
1033
1038
1039
1044
1046{
1047 if (bufferProgress == m_bufferProgress)
1048 return;
1049
1050 auto status = m_mediaStatus;
1051 // Buffered -> unbuffered.
1052 if (!bufferProgress) {
1054 } else if (status == QMediaPlayer::StalledMedia) {
1056 // Resume playback.
1057 if (m_state == QMediaPlayer::PlayingState) {
1058 [[static_cast<AVFMediaPlayerObserver*>(m_observer) player] setRate:m_rate];
1059 m_playbackTimer.start();
1060 }
1061 }
1062
1063 if (m_mediaStatus != status)
1064 Q_EMIT mediaStatusChanged(m_mediaStatus = status);
1065
1066 m_bufferProgress = bufferProgress;
1068}
1069
1071{
1072 if (duration == m_duration)
1073 return;
1074
1075 m_duration = duration;
1077}
1078
1080{
1081 if (m_state == QMediaPlayer::StoppedState)
1082 return;
1083
1085}
1086
1088{
1089 if (m_requestedPosition != -1) {
1090 m_requestedPosition = -1;
1092 }
1093
1095
1096 Q_EMIT error(QMediaPlayer::FormatError, tr("Failed to load media"));
1097}
1098
1100{
1101 setStreamURL(m_observer, m_resources.toEncoded());
1102}
1103
1105{
1106 resetStream(nullptr);
1107}
1108
1110{
1111 bool firstLoad = true;
1112 for (int i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
1113 if (tracks[i].count())
1114 firstLoad = false;
1115 tracks[i].clear();
1116 nativeTracks[i].clear();
1117 }
1118 AVPlayerItem *playerItem = [m_observer playerItem];
1119 if (playerItem) {
1120 // Check each track for audio and video content
1121 NSArray *tracks = playerItem.tracks;
1122 for (AVPlayerItemTrack *track in tracks) {
1123 AVAssetTrack *assetTrack = track.assetTrack;
1124 if (assetTrack) {
1125 int qtTrack = -1;
1126 if ([assetTrack.mediaType isEqualToString:AVMediaTypeAudio]) {
1128 setAudioAvailable(true);
1129 } else if ([assetTrack.mediaType isEqualToString:AVMediaTypeVideo]) {
1131 setVideoAvailable(true);
1132 if (m_observer.videoTrack != track) {
1133 m_observer.videoTrack = track;
1134 bool isMirrored = false;
1136 videoOrientationForAssetTrack(assetTrack, orientation, isMirrored);
1137 orientationChanged(orientation, isMirrored);
1138 }
1139 }
1140 else if ([assetTrack.mediaType isEqualToString:AVMediaTypeSubtitle]) {
1142 }
1143 if (qtTrack != -1) {
1145 this->tracks[qtTrack].append(metaData);
1146 nativeTracks[qtTrack].append(track);
1147 }
1148 }
1149 }
1150 // subtitles are disabled by default
1151 if (firstLoad)
1153 }
1155}
1156
1158{
1159 const auto &t = nativeTracks[type];
1161 // subtitle streams are not always automatically enabled on macOS/iOS.
1162 // this hack ensures they get enables and we actually get the text
1163 AVPlayerItem *playerItem = m_observer.m_playerItem;
1164 if (playerItem) {
1165 AVAsset *asset = playerItem.asset;
1166 if (!asset)
1167 return;
1168 AVMediaSelectionGroup *group = [asset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
1169 if (!group)
1170 return;
1171 auto *options = group.options;
1172 if (options.count)
1173 [playerItem selectMediaOption:options.firstObject inMediaSelectionGroup:group];
1174 }
1175 }
1176 for (int i = 0; i < t.count(); ++i)
1177 t.at(i).enabled = (i == index);
1179}
1180
1182{
1183 const auto &t = nativeTracks[type];
1184 for (int i = 0; i < t.count(); ++i)
1185 if (t.at(i).enabled)
1186 return i;
1187 return -1;
1188}
1189
1194
1196{
1197 const auto &t = tracks[type];
1198 if (trackNumber < 0 || trackNumber >= t.count())
1199 return QMediaMetaData();
1200 return t.at(trackNumber);
1201}
1202
1203void AVFMediaPlayer::resetStream(QIODevice *stream)
1204{
1205 if (m_mediaStream) {
1208 }
1209
1210 m_mediaStream = stream;
1211
1212 if (m_mediaStream) {
1215 }
1216}
1217
1219{
1220 if (!m_videoSink)
1221 return;
1222 m_videoSink->setNativeSize(size);
1223}
1224
1225void AVFMediaPlayer::orientationChanged(QtVideo::Rotation rotation, bool mirrored)
1226{
1227 if (!m_videoOutput)
1228 return;
1229
1230 m_videoOutput->setVideoRotation(rotation);
1231 m_videoOutput->setVideoMirrored(mirrored);
1232}
1233
1236 bool &mirrored)
1237{
1239 if (videoTrack) {
1240 CGAffineTransform transform = videoTrack.preferredTransform;
1241 if (CGAffineTransformIsIdentity(transform))
1242 return;
1243 qreal delta = transform.a * transform.d - transform.b * transform.c;
1244 qreal radians = qAtan2(transform.b, transform.a);
1245 qreal degrees = qRadiansToDegrees(radians);
1246 qreal scaleX = (transform.a/qAbs(transform.a)) * qSqrt(qPow(transform.a, 2) + qPow(transform.c, 2));
1247 qreal scaleY = (transform.d/abs(transform.d)) * qSqrt(qPow(transform.b, 2) + qPow(transform.d, 2));
1248
1249 if (delta < 0.0) { // flipped
1250 if (scaleX < 0.0) {
1251 // vertical flip
1252 degrees = -degrees;
1253 } else if (scaleY < 0.0) {
1254 // horizontal flip
1255 degrees = (180 + (int)degrees) % 360;
1256 }
1257 mirrored = true;
1258 }
1259
1260 if (qFuzzyCompare(degrees, qreal(90)) || qFuzzyCompare(degrees, qreal(-270))) {
1262 } else if (qFuzzyCompare(degrees, qreal(-90)) || qFuzzyCompare(degrees, qreal(270))) {
1264 } else if (qFuzzyCompare(degrees, qreal(180)) || qFuzzyCompare(degrees, qreal(-180))) {
1266 }
1267 }
1268}
1269
1270#include "moc_avfmediaplayer_p.cpp"
QMediaPlayer player
Definition audio.cpp:213
static void * AVFMediaPlayerObserverStatusObservationContext
static NSString *const AVF_RATE_KEY
static void * AVFMediaPlayerObserverCurrentItemObservationContext
static NSString *const AVF_PLAYABLE_KEY
static NSString *const AVF_STATUS_KEY
static void * AVFMediaPlayerObserverRateObservationContext
static NSString *const AVF_CURRENT_ITEM_KEY
static void setURL(AVFMediaPlayerObserver *observer, const QByteArray &url, const QString &mimeType=QString())
static void * AVFMediaPlayerObserverPresentationSizeContext
static void * AVFMediaPlayerObserverBufferLikelyToKeepUpContext
NSURL * m_URL
static QT_USE_NAMESPACE NSString *const AVF_TRACKS_KEY
static NSString *const AVF_BUFFER_LIKELY_KEEP_UP_KEY
static NSString *const AVF_CURRENT_ITEM_DURATION_KEY
NSString * m_mimeType
static void * AVFMediaPlayerObserverTracksContext
static void setStreamURL(AVFMediaPlayerObserver *observer, const QByteArray &url)
static void * AVFMediaPlayerObserverCurrentItemDurationObservationContext
BOOL m_bufferIsLikelyToKeepUp
NSData * m_data
IOBluetoothDevice * device
QMediaMetaData trackMetaData(TrackType type, int trackNumber) override
qint64 duration() const override
virtual ~AVFMediaPlayer()
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)
static void videoOrientationForAssetTrack(AVAssetTrack *track, QtVideo::Rotation &angle, bool &mirrored)
bool isAudioAvailable() const override
QMediaMetaData metaData() const override
void setVideoOutput(AVFVideoRendererControl *output)
void stop() override
float bufferProgress() const override
void setMedia(const QUrl &content, QIODevice *stream) override
qint64 position() const override
int activeTrack(QPlatformMediaPlayer::TrackType type) override
QMediaPlayer::PlaybackState state() const override
void setMuted(bool muted)
void pause() override
QPlatformAudioOutput * m_audioOutput
void play() override
void processDurationChange(qint64 duration)
QUrl media() const override
QList< AVPlayerItemTrack * > nativeTracks[QPlatformMediaPlayer::NTrackTypes]
void processLoadStateFailure()
qreal playbackRate() const override
QMediaPlayer::MediaStatus mediaStatus() const override
QIODevice * mediaStream() const override
AVAsset * currentAssetHandle()
bool isVideoAvailable() const override
bool isSeekable() const override
void setAudioOutput(QPlatformAudioOutput *output) override
void setActiveTrack(QPlatformMediaPlayer::TrackType type, int index) override
AVFMediaPlayer(QMediaPlayer *parent)
QList< QMediaMetaData > tracks[QPlatformMediaPlayer::NTrackTypes]
void processBufferStateChange(int bufferProgress)
void setPlaybackRate(qreal rate) override
static QMediaMetaData fromAssetTrack(AVAssetTrack *asset)
static QMediaMetaData fromAsset(AVAsset *asset)
void setVideoRotation(QtVideo::Rotation)
void setLayer(CALayer *layer) override
void setVideoSink(AVFVideoSink *sink)
void setNativeSize(QSize size)
QByteArray id
\qmlproperty string QtMultimedia::audioDevice::id
void deviceChanged()
void mutedChanged(bool muted)
void volumeChanged(float volume)
\inmodule QtCore
Definition qbytearray.h:57
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:107
\inmodule QtCore \reentrant
Definition qiodevice.h:34
void readyRead()
This signal is emitted once every time new data is available for reading from the device's current re...
virtual qint64 size() const
For open random-access devices, this function returns the size of the device.
qsizetype count() const noexcept
Definition qlist.h:398
void append(parameter_type t)
Definition qlist.h:458
void clear()
Definition qlist.h:434
\inmodule QtMultimedia
Q_INVOKABLE bool isEmpty() const
\qmlmethod bool QtMultimedia::mediaMetaData::isEmpty() Returns true if the meta data contains no item...
Q_INVOKABLE void clear()
\qmlmethod void QtMultimedia::mediaMetaData::clear() Removes all data from the MediaMetaData object.
The QMediaPlayer class allows the playing of a media files.
MediaStatus
\qmlproperty enumeration QtMultimedia::MediaPlayer::playbackState
PlaybackState
Defines the current state of a media player.
The QMediaTimeRange class represents a set of zero or more disjoint time intervals.
\inmodule QtCore
Definition qmutex.h:313
\inmodule QtCore
Definition qmutex.h:281
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
static bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *member)
\threadsafe
Definition qobject.cpp:3236
void destroyed(QObject *=nullptr)
This signal is emitted immediately before the object obj is destroyed, after any instances of QPointe...
void positionChanged(qint64 position)
void seekableChanged(bool seekable)
void durationChanged(qint64 duration)
void playbackRateChanged(qreal rate)
void audioAvailableChanged(bool audioAvailable)
void stateChanged(QMediaPlayer::PlaybackState newState)
void videoAvailableChanged(bool videoAvailable)
void mediaStatusChanged(QMediaPlayer::MediaStatus status)
void bufferProgressChanged(float progress)
\inmodule QtCore
Definition qsize.h:25
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
void stop()
Stops the timer.
Definition qtimer.cpp:267
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
\inmodule QtCore
Definition qurl.h:94
QString url(FormattingOptions options=FormattingOptions(PrettyDecoded)) const
Returns a string representation of the URL.
Definition qurl.cpp:2817
QByteArray toEncoded(FormattingOptions options=FullyEncoded) const
Returns the encoded representation of the URL if it's valid; otherwise an empty QByteArray is returne...
Definition qurl.cpp:2967
bool isEmpty() const
Returns true if the URL has no data; otherwise returns false.
Definition qurl.cpp:1896
The QVideoSink class represents a generic sink for video data.
Definition qvideosink.h:22
QString str
[2]
void newState(QList< State > &states, const char *token, const char *lexem, bool pre)
AVFMediaPlayer * m_session
void setURL:mimeType:(NSURL *url,[mimeType] NSString *mimeType)
AVPlayerLayer * m_playerLayer
AVPlayerItem * m_playerItem
AVPlayerItemTrack * videoTrack
@ AutoConnection
QString self
Definition language.cpp:58
static void * context
qint64 startTime
#define QByteArrayLiteral(str)
Definition qbytearray.h:52
long NSInteger
#define Q_FUNC_INFO
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
DBusConnection const char DBusError * error
EGLStreamKHR stream
const char * mimeType
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
qfloat16 qSqrt(qfloat16 f)
Definition qfloat16.h:289
#define qDebug
[1]
Definition qlogging.h:164
#define qWarning
Definition qlogging.h:166
constexpr float qRadiansToDegrees(float radians)
Definition qmath.h:281
auto qAtan2(T1 y, T2 x)
Definition qmath.h:90
auto qPow(T1 x, T2 y)
Definition qmath.h:180
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
#define Q_ARG(Type, data)
Definition qobjectdefs.h:63
n void setPosition(void) \n\
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint index
[2]
GLenum GLenum GLsizei count
GLuint object
[3]
GLenum GLuint buffer
GLenum type
GLboolean GLuint group
GLfloat angle
GLuint GLenum GLenum transform
GLuint GLenum * rate
GLdouble GLdouble t
Definition qopenglext.h:243
GLuint in
GLsizei GLenum GLboolean sink
GLenum GLsizei len
#define tr(X)
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
#define Q_EMIT
#define emit
#define Q_UNUSED(x)
unsigned long long quint64
Definition qtypes.h:61
long long qint64
Definition qtypes.h:60
double qreal
Definition qtypes.h:187
QT_BEGIN_NAMESPACE typedef uchar * output
QUrl url("example.com")
[constructor-url-reference]
QObject::connect nullptr
myObject disconnect()
[26]
QReadWriteLock lock
[0]
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...