Qt 6.x
The Qt SDK
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
16#import <AVFoundation/AVFoundation.h>
17
19
20//AVAsset Keys
21static NSString* const AVF_TRACKS_KEY = @"tracks";
22static NSString* const AVF_PLAYABLE_KEY = @"playable";
23
24//AVPlayerItem keys
25static NSString* const AVF_STATUS_KEY = @"status";
26static NSString* const AVF_BUFFER_LIKELY_KEEP_UP_KEY = @"playbackLikelyToKeepUp";
27
28//AVPlayer keys
29static NSString* const AVF_RATE_KEY = @"rate";
30static NSString* const AVF_CURRENT_ITEM_KEY = @"currentItem";
31static NSString* const AVF_CURRENT_ITEM_DURATION_KEY = @"currentItem.duration";
32
40
41@interface AVFMediaPlayerObserver : NSObject<AVAssetResourceLoaderDelegate>
42
43@property (readonly, getter=player) AVPlayer* m_player;
44@property (readonly, getter=playerItem) AVPlayerItem* m_playerItem;
45@property (readonly, getter=playerLayer) AVPlayerLayer* m_playerLayer;
46@property (readonly, getter=session) AVFMediaPlayer* m_session;
47@property (retain) AVPlayerItemTrack *videoTrack;
48
49- (AVFMediaPlayerObserver *) initWithMediaPlayerSession:(AVFMediaPlayer *)session;
50- (void) setURL:(NSURL *)url mimeType:(NSString *)mimeType;
52- (void) prepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys;
53- (void) assetFailedToPrepareForPlayback:(NSError *)error;
54- (void) playerItemDidReachEnd:(NSNotification *)notification;
55- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
56 change:(NSDictionary *)change context:(void *)context;
58- (void) dealloc;
59- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest;
60@end
61
62@implementation AVFMediaPlayerObserver
63{
64@private
66 AVPlayer *m_player;
67 AVPlayerItem *m_playerItem;
68 AVPlayerLayer *m_playerLayer;
69 NSURL *m_URL;
71 NSData *m_data;
72 NSString *m_mimeType;
73}
74
76
77- (AVFMediaPlayerObserver *) initWithMediaPlayerSession:(AVFMediaPlayer *)session
78{
79 if (!(self = [super init]))
80 return nil;
81
82 m_session = session;
84
85 m_playerLayer = [AVPlayerLayer playerLayerWithPlayer:nil];
86 [m_playerLayer retain];
87 m_playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
88 m_playerLayer.anchorPoint = CGPointMake(0.0f, 0.0f);
89 return self;
90}
91
92- (void) setURL:(NSURL *)url mimeType:(NSString *)mimeType
93{
94 if (!m_session)
95 return;
96
97 [m_mimeType release];
98 m_mimeType = [mimeType retain];
99
100 if (m_URL != url)
101 {
102 [m_URL release];
103 m_URL = [url copy];
104
105 //Create an asset for inspection of a resource referenced by a given URL.
106 //Load the values for the asset keys "tracks", "playable".
107
108 // use __block to avoid maintaining strong references on variables captured by the
109 // following block callback
110#if defined(Q_OS_IOS)
111 BOOL isAccessing = [m_URL startAccessingSecurityScopedResource];
112#endif
113 __block AVURLAsset *asset = [[AVURLAsset URLAssetWithURL:m_URL options:nil] retain];
114 [asset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()];
115
116 __block NSArray *requestedKeys = [[NSArray arrayWithObjects:AVF_TRACKS_KEY, AVF_PLAYABLE_KEY, nil] retain];
117
118 __block AVFMediaPlayerObserver *blockSelf = [self retain];
119
120 // Tells the asset to load the values of any of the specified keys that are not already loaded.
121 [asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:
122 ^{
123 dispatch_async( dispatch_get_main_queue(),
124 ^{
125#if defined(Q_OS_IOS)
126 if (isAccessing)
127 [m_URL stopAccessingSecurityScopedResource];
128#endif
129 [blockSelf prepareToPlayAsset:asset withKeys:requestedKeys];
130 [asset release];
131 [requestedKeys release];
132 [blockSelf release];
133 });
134 }];
135 }
136}
137
139{
140 if (m_playerItem) {
141 [m_playerItem removeObserver:self forKeyPath:@"presentationSize"];
142 [m_playerItem removeObserver:self forKeyPath:AVF_STATUS_KEY];
143 [m_playerItem removeObserver:self forKeyPath:AVF_BUFFER_LIKELY_KEEP_UP_KEY];
144 [m_playerItem removeObserver:self forKeyPath:AVF_TRACKS_KEY];
145
146 [[NSNotificationCenter defaultCenter] removeObserver:self
147 name:AVPlayerItemDidPlayToEndTimeNotification
148 object:m_playerItem];
149 m_playerItem = 0;
150 }
151 if (m_player) {
152 [m_player setRate:0.0];
153 [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_DURATION_KEY];
154 [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_KEY];
155 [m_player removeObserver:self forKeyPath:AVF_RATE_KEY];
156 [m_player release];
157 m_player = 0;
158 }
159 if (m_playerLayer)
160 m_playerLayer.player = nil;
161#if defined(Q_OS_IOS)
162 [[AVAudioSession sharedInstance] setActive:NO error:nil];
163#endif
164}
165
166- (void) prepareToPlayAsset:(AVURLAsset *)asset
167 withKeys:(NSArray *)requestedKeys
168{
169 if (!m_session)
170 return;
171
172 //Make sure that the value of each key has loaded successfully.
173 for (NSString *thisKey in requestedKeys)
174 {
175 NSError *error = nil;
176 AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
177#ifdef QT_DEBUG_AVF
178 qDebug() << Q_FUNC_INFO << [thisKey UTF8String] << " status: " << keyStatus;
179#endif
180 if (keyStatus == AVKeyValueStatusFailed)
181 {
182 [self assetFailedToPrepareForPlayback:error];
183 return;
184 }
185 }
186
187 //Use the AVAsset playable property to detect whether the asset can be played.
188#ifdef QT_DEBUG_AVF
189 qDebug() << Q_FUNC_INFO << "isPlayable: " << [asset isPlayable];
190#endif
191 if (!asset.playable)
192 qWarning() << "Asset reported to be not playable. Playback of this asset may not be possible.";
193
194 //At this point we're ready to set up for playback of the asset.
195 //Stop observing our prior AVPlayerItem, if we have one.
196 if (m_playerItem)
197 {
198 //Remove existing player item key value observers and notifications.
199 [self unloadMedia];
200 }
201
202 //Create a new instance of AVPlayerItem from the now successfully loaded AVAsset.
203 m_playerItem = [AVPlayerItem playerItemWithAsset:asset];
204 if (!m_playerItem) {
205 qWarning() << "Failed to create player item";
206 //Generate an error describing the failure.
207 NSString *localizedDescription = NSLocalizedString(@"Item cannot be played", @"Item cannot be played description");
208 NSString *localizedFailureReason = NSLocalizedString(@"The assets tracks were loaded, but couldn't create player item.", @"Item cannot be played failure reason");
209 NSDictionary *errorDict = [NSDictionary dictionaryWithObjectsAndKeys:
210 localizedDescription, NSLocalizedDescriptionKey,
211 localizedFailureReason, NSLocalizedFailureReasonErrorKey,
212 nil];
213 NSError *assetCannotBePlayedError = [NSError errorWithDomain:@"StitchedStreamPlayer" code:0 userInfo:errorDict];
214
215 [self assetFailedToPrepareForPlayback:assetCannotBePlayedError];
216 return;
217 }
218
219 //Observe the player item "status" key to determine when it is ready to play.
220 [m_playerItem addObserver:self
221 forKeyPath:AVF_STATUS_KEY
222 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
223 context:AVFMediaPlayerObserverStatusObservationContext];
224
225 [m_playerItem addObserver:self
226 forKeyPath:@"presentationSize"
227 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
228 context:AVFMediaPlayerObserverPresentationSizeContext];
229
230 [m_playerItem addObserver:self
231 forKeyPath:AVF_BUFFER_LIKELY_KEEP_UP_KEY
232 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
233 context:AVFMediaPlayerObserverBufferLikelyToKeepUpContext];
234
235 [m_playerItem addObserver:self
236 forKeyPath:AVF_TRACKS_KEY
237 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
238 context:AVFMediaPlayerObserverTracksContext];
239
240 //When the player item has played to its end time we'll toggle
241 //the movie controller Pause button to be the Play button
242 [[NSNotificationCenter defaultCenter] addObserver:self
243 selector:@selector(playerItemDidReachEnd:)
244 name:AVPlayerItemDidPlayToEndTimeNotification
245 object:m_playerItem];
246
247 //Get a new AVPlayer initialized to play the specified player item.
248 m_player = [AVPlayer playerWithPlayerItem:m_playerItem];
249 [m_player retain];
250
251 //Set the initial volume on new player object
252 if (self.session) {
253 auto *audioOutput = m_session->m_audioOutput;
254 m_player.volume = (audioOutput ? audioOutput->volume : 1.);
255 m_player.muted = (audioOutput ? audioOutput->muted : true);
256 }
257
258 //Assign the output layer to the new player
259 m_playerLayer.player = m_player;
260
261 //Observe the AVPlayer "currentItem" property to find out when any
262 //AVPlayer replaceCurrentItemWithPlayerItem: replacement will/did
263 //occur.
264 [m_player addObserver:self
265 forKeyPath:AVF_CURRENT_ITEM_KEY
266 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
267 context:AVFMediaPlayerObserverCurrentItemObservationContext];
268
269 //Observe the AVPlayer "rate" property to update the scrubber control.
270 [m_player addObserver:self
271 forKeyPath:AVF_RATE_KEY
272 options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
273 context:AVFMediaPlayerObserverRateObservationContext];
274
275 //Observe the duration for getting the buffer state
276 [m_player addObserver:self
277 forKeyPath:AVF_CURRENT_ITEM_DURATION_KEY
278 options:0
279 context:AVFMediaPlayerObserverCurrentItemDurationObservationContext];
280#if defined(Q_OS_IOS)
281 [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
282 [[AVAudioSession sharedInstance] setActive:YES error:nil];
283#endif
284}
285
286-(void) assetFailedToPrepareForPlayback:(NSError *)error
287{
289 QMetaObject::invokeMethod(m_session, "processMediaLoadError", Qt::AutoConnection);
290#ifdef QT_DEBUG_AVF
291 qDebug() << Q_FUNC_INFO;
292 qDebug() << [[error localizedDescription] UTF8String];
293 qDebug() << [[error localizedFailureReason] UTF8String];
294 qDebug() << [[error localizedRecoverySuggestion] UTF8String];
295#endif
296}
297
298- (void) playerItemDidReachEnd:(NSNotification *)notification
299{
300 Q_UNUSED(notification);
301 if (self.session)
303}
304
305- (void) observeValueForKeyPath:(NSString*) path
306 ofObject:(id)object
307 change:(NSDictionary*)change
308 context:(void*)context
309{
310 //AVPlayerItem "status" property value observer.
312 {
313 AVPlayerStatus status = (AVPlayerStatus)[[change objectForKey:NSKeyValueChangeNewKey] integerValue];
314 switch (status)
315 {
316 //Indicates that the status of the player is not yet known because
317 //it has not tried to load new media resources for playback
318 case AVPlayerStatusUnknown:
319 {
320 //QMetaObject::invokeMethod(m_session, "processLoadStateChange", Qt::AutoConnection);
321 }
322 break;
323
324 case AVPlayerStatusReadyToPlay:
325 {
326 //Once the AVPlayerItem becomes ready to play, i.e.
327 //[playerItem status] == AVPlayerItemStatusReadyToPlay,
328 //its duration can be fetched from the item.
329 if (self.session)
330 QMetaObject::invokeMethod(m_session, "processLoadStateChange", Qt::AutoConnection);
331 }
332 break;
333
334 case AVPlayerStatusFailed:
335 {
336 AVPlayerItem *playerItem = static_cast<AVPlayerItem*>(object);
337 [self assetFailedToPrepareForPlayback:playerItem.error];
338
339 if (self.session)
340 QMetaObject::invokeMethod(m_session, "processLoadStateFailure", Qt::AutoConnection);
341 }
342 break;
343 }
345 QSize size(m_playerItem.presentationSize.width, m_playerItem.presentationSize.height);
348 {
349 const bool isPlaybackLikelyToKeepUp = [m_playerItem isPlaybackLikelyToKeepUp];
350 if (isPlaybackLikelyToKeepUp != m_bufferIsLikelyToKeepUp) {
351 m_bufferIsLikelyToKeepUp = isPlaybackLikelyToKeepUp;
352 QMetaObject::invokeMethod(m_session, "processBufferStateChange", Qt::AutoConnection,
353 Q_ARG(int, isPlaybackLikelyToKeepUp ? 100 : 0));
354 }
355 }
357 {
359 }
360 //AVPlayer "rate" property value observer.
362 {
363 //QMetaObject::invokeMethod(m_session, "setPlaybackRate", Qt::AutoConnection, Q_ARG(qreal, [m_player rate]));
364 }
365 //AVPlayer "currentItem" property observer.
366 //Called when the AVPlayer replaceCurrentItemWithPlayerItem:
367 //replacement will/did occur.
369 {
370 AVPlayerItem *newPlayerItem = [change objectForKey:NSKeyValueChangeNewKey];
371 if (m_playerItem != newPlayerItem)
372 m_playerItem = newPlayerItem;
373 }
375 {
376 const CMTime time = [m_playerItem duration];
377 const qint64 duration = static_cast<qint64>(float(time.value) / float(time.timescale) * 1000.0f);
378 if (self.session)
379 QMetaObject::invokeMethod(m_session, "processDurationChange", Qt::AutoConnection, Q_ARG(qint64, duration));
380 }
381 else
382 {
383 [super observeValueForKeyPath:path ofObject:object change:change context:context];
384 }
385}
386
388{
389#ifdef QT_DEBUG_AVF
390 qDebug() << Q_FUNC_INFO;
391#endif
392 m_session = 0;
393}
394
395- (void) dealloc
396{
397#ifdef QT_DEBUG_AVF
398 qDebug() << Q_FUNC_INFO;
399#endif
400 [self unloadMedia];
401
402 if (m_URL) {
403 [m_URL release];
404 }
405
406 [m_mimeType release];
407 [m_playerLayer release];
408 [super dealloc];
409}
410
411- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
412{
413 Q_UNUSED(resourceLoader);
414
415 if (![loadingRequest.request.URL.scheme isEqualToString:@"iodevice"])
416 return NO;
417
419 if (!device)
420 return NO;
421
422 device->seek(loadingRequest.dataRequest.requestedOffset);
423 if (loadingRequest.contentInformationRequest) {
424 loadingRequest.contentInformationRequest.contentType = m_mimeType;
425 loadingRequest.contentInformationRequest.contentLength = device->size();
426 loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;
427 }
428
429 if (loadingRequest.dataRequest) {
430 NSInteger requestedLength = loadingRequest.dataRequest.requestedLength;
431 int maxBytes = qMin(32 * 1064, int(requestedLength));
432 char buffer[maxBytes];
433 NSInteger submitted = 0;
434 while (submitted < requestedLength) {
435 qint64 len = device->read(buffer, maxBytes);
436 if (len < 1)
437 break;
438
439 [loadingRequest.dataRequest respondWithData:[NSData dataWithBytes:buffer length:len]];
440 submitted += len;
441 }
442
443 // Finish loading even if not all bytes submitted.
444 [loadingRequest finishLoading];
445 }
446
447 return YES;
448}
449@end
450
452 : QObject(player),
454 m_state(QMediaPlayer::StoppedState),
455 m_mediaStatus(QMediaPlayer::NoMedia),
456 m_mediaStream(nullptr),
457 m_rate(1.0),
458 m_requestedPosition(-1),
459 m_duration(0),
460 m_bufferProgress(0),
461 m_videoAvailable(false),
462 m_audioAvailable(false),
463 m_seekable(false)
464{
465 m_observer = [[AVFMediaPlayerObserver alloc] initWithMediaPlayerSession:this];
468}
469
471{
472#ifdef QT_DEBUG_AVF
473 qDebug() << Q_FUNC_INFO;
474#endif
475 //Detatch the session from the sessionObserver (which could still be alive trying to communicate with this session).
477 [static_cast<AVFMediaPlayerObserver*>(m_observer) release];
478}
479
481{
482 m_videoSink = sink ? static_cast<AVFVideoSink *>(sink->platformVideoSink()): nullptr;
483 m_videoOutput->setVideoSink(m_videoSink);
484}
485
487{
488#ifdef QT_DEBUG_AVF
489 qDebug() << Q_FUNC_INFO << output;
490#endif
491
492 if (m_videoOutput == output)
493 return;
494
495 //Set the current output layer to null to stop rendering
496 if (m_videoOutput) {
497 m_videoOutput->setLayer(nullptr);
498 }
499
500 m_videoOutput = output;
501
502 if (m_videoOutput && m_state != QMediaPlayer::StoppedState)
503 m_videoOutput->setLayer([static_cast<AVFMediaPlayerObserver*>(m_observer) playerLayer]);
504}
505
507{
508#ifdef QT_DEBUG_AVF
509 qDebug() << Q_FUNC_INFO;
510#endif
511 AVAsset *currentAsset = [[static_cast<AVFMediaPlayerObserver*>(m_observer) playerItem] asset];
512 return currentAsset;
513}
514
516{
517 return m_state;
518}
519
521{
522 return m_mediaStatus;
523}
524
526{
527 return m_resources;
528}
529
531{
532 return m_mediaStream;
533}
534
535static void setURL(AVFMediaPlayerObserver *observer, const QByteArray &url, const QString &mimeType = QString())
536{
537 NSString *urlString = [NSString stringWithUTF8String:url.constData()];
538 NSURL *nsurl = [NSURL URLWithString:urlString];
539 [observer setURL:nsurl mimeType:[NSString stringWithUTF8String:mimeType.toLatin1().constData()]];
540}
541
542static void setStreamURL(AVFMediaPlayerObserver *observer, const QByteArray &url)
543{
544 setURL(observer, QByteArrayLiteral("iodevice://") + url, QFileInfo(QString::fromUtf8(url)).suffix());
545}
546
548{
549#ifdef QT_DEBUG_AVF
550 qDebug() << Q_FUNC_INFO << content.request().url();
551#endif
552
553 [static_cast<AVFMediaPlayerObserver*>(m_observer) unloadMedia];
554
555 m_resources = content;
556 resetStream(stream);
557
558 setAudioAvailable(false);
559 setVideoAvailable(false);
560 setSeekable(false);
561 m_requestedPosition = -1;
562 orientationChanged(QVideoFrame::Rotation0, false);
564 if (m_duration != 0) {
565 m_duration = 0;
567 }
568 if (!m_metaData.isEmpty()) {
569 m_metaData.clear();
571 }
572 for (int i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
573 tracks[i].clear();
574 nativeTracks[i].clear();
575 }
577
578 const QMediaPlayer::MediaStatus oldMediaStatus = m_mediaStatus;
579 const QMediaPlayer::PlaybackState oldState = m_state;
580
581 if (!m_mediaStream && content.isEmpty()) {
582 m_mediaStatus = QMediaPlayer::NoMedia;
583 if (m_mediaStatus != oldMediaStatus)
584 Q_EMIT mediaStatusChanged(m_mediaStatus);
585
587 if (m_state != oldState)
588 Q_EMIT stateChanged(m_state);
589
590 return;
591 }
592
593 m_mediaStatus = QMediaPlayer::LoadingMedia;
594 if (m_mediaStatus != oldMediaStatus)
595 Q_EMIT mediaStatusChanged(m_mediaStatus);
596
597 if (m_mediaStream) {
598 // If there is a data, try to load it,
599 // otherwise wait for readyRead.
600 if (m_mediaStream->size())
601 setStreamURL(m_observer, m_resources.toEncoded());
602 } else {
603 //Load AVURLAsset
604 //initialize asset using content's URL
605 setURL(m_observer, m_resources.toEncoded());
606 }
607
609 if (m_state != oldState)
610 Q_EMIT stateChanged(m_state);
611}
612
614{
615 AVPlayerItem *playerItem = [static_cast<AVFMediaPlayerObserver*>(m_observer) playerItem];
616
617 if (m_requestedPosition != -1)
618 return m_requestedPosition;
619
620 if (!playerItem)
621 return 0;
622
623 CMTime time = [playerItem currentTime];
624 return static_cast<quint64>(float(time.value) / float(time.timescale) * 1000.0f);
625}
626
628{
629#ifdef QT_DEBUG_AVF
630 qDebug() << Q_FUNC_INFO;
631#endif
632 return m_duration;
633}
634
636{
637#ifdef QT_DEBUG_AVF
638 qDebug() << Q_FUNC_INFO;
639#endif
640 return m_bufferProgress/100.;
641}
642
643void AVFMediaPlayer::setAudioAvailable(bool available)
644{
645 if (m_audioAvailable == available)
646 return;
647
648 m_audioAvailable = available;
649 Q_EMIT audioAvailableChanged(available);
650}
651
653{
654 return m_audioAvailable;
655}
656
657void AVFMediaPlayer::setVideoAvailable(bool available)
658{
659 if (m_videoAvailable == available)
660 return;
661
662 m_videoAvailable = available;
663 Q_EMIT videoAvailableChanged(available);
664}
665
667{
668 return m_videoAvailable;
669}
670
672{
673 return m_seekable;
674}
675
676void AVFMediaPlayer::setSeekable(bool seekable)
677{
678 if (m_seekable == seekable)
679 return;
680
681 m_seekable = seekable;
682 Q_EMIT seekableChanged(seekable);
683}
684
686{
687 AVPlayerItem *playerItem = [static_cast<AVFMediaPlayerObserver*>(m_observer) playerItem];
688
689 if (playerItem) {
690 QMediaTimeRange timeRanges;
691
692 NSArray *ranges = [playerItem loadedTimeRanges];
693 for (NSValue *timeRange in ranges) {
694 CMTimeRange currentTimeRange = [timeRange CMTimeRangeValue];
695 qint64 startTime = qint64(float(currentTimeRange.start.value) / currentTimeRange.start.timescale * 1000.0);
696 timeRanges.addInterval(startTime, startTime + qint64(float(currentTimeRange.duration.value) / currentTimeRange.duration.timescale * 1000.0));
697 }
698 if (!timeRanges.isEmpty())
699 return timeRanges;
700 }
701 return QMediaTimeRange(0, duration());
702}
703
705{
706 return m_rate;
707}
708
710{
711 if (m_audioOutput == output)
712 return;
713 if (m_audioOutput)
714 m_audioOutput->q->disconnect(this);
716 if (m_audioOutput) {
720 //connect(m_audioOutput->q, &QAudioOutput::audioRoleChanged, this, &AVFMediaPlayer::setAudioRole);
721 }
725}
726
728{
729 return m_metaData;
730}
731
733{
734#ifdef QT_DEBUG_AVF
735 qDebug() << Q_FUNC_INFO << rate;
736#endif
737
738 if (qFuzzyCompare(m_rate, rate))
739 return;
740
741 m_rate = rate;
742
743 AVPlayer *player = [static_cast<AVFMediaPlayerObserver*>(m_observer) player];
744 if (player && m_state == QMediaPlayer::PlayingState)
745 [player setRate:m_rate];
746
748}
749
751{
752#ifdef QT_DEBUG_AVF
753 qDebug() << Q_FUNC_INFO << pos;
754#endif
755
756 if (pos == position())
757 return;
758
759 AVPlayerItem *playerItem = [static_cast<AVFMediaPlayerObserver*>(m_observer) playerItem];
760 if (!playerItem) {
761 m_requestedPosition = pos;
762 Q_EMIT positionChanged(m_requestedPosition);
763 return;
764 }
765
766 if (!isSeekable()) {
767 if (m_requestedPosition != -1) {
768 m_requestedPosition = -1;
770 }
771 return;
772 }
773
774 pos = qMax(qint64(0), pos);
775 if (duration() > 0)
776 pos = qMin(pos, duration());
777 m_requestedPosition = pos;
778
779 CMTime newTime = [playerItem currentTime];
780 newTime.value = (pos / 1000.0f) * newTime.timescale;
781 [playerItem seekToTime:newTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero
782 completionHandler:^(BOOL finished) {
783 if (finished)
784 m_requestedPosition = -1;
785 }];
786
788
789 // Reset media status if the current status is EndOfMedia
790 if (m_mediaStatus == QMediaPlayer::EndOfMedia) {
793 Q_EMIT mediaStatusChanged((m_mediaStatus = newMediaStatus));
794 }
795}
796
798{
799#ifdef QT_DEBUG_AVF
800 qDebug() << Q_FUNC_INFO << "currently: " << m_state;
801#endif
802
803 if (m_mediaStatus == QMediaPlayer::NoMedia || m_mediaStatus == QMediaPlayer::InvalidMedia)
804 return;
805
806 if (m_state == QMediaPlayer::PlayingState)
807 return;
808
810
811 if (m_videoOutput && m_videoSink)
812 m_videoOutput->setLayer([static_cast<AVFMediaPlayerObserver*>(m_observer) playerLayer]);
813
814 // Reset media status if the current status is EndOfMedia
815 if (m_mediaStatus == QMediaPlayer::EndOfMedia)
816 setPosition(0);
817
818 if (m_mediaStatus == QMediaPlayer::LoadedMedia || m_mediaStatus == QMediaPlayer::BufferedMedia) {
819 // Setting the rate starts playback
820 [[static_cast<AVFMediaPlayerObserver*>(m_observer) player] setRate:m_rate];
821 }
822
825
826 Q_EMIT stateChanged(m_state);
827 m_playbackTimer.start(100);
828}
829
831{
832#ifdef QT_DEBUG_AVF
833 qDebug() << Q_FUNC_INFO << "currently: " << m_state;
834#endif
835
836 if (m_mediaStatus == QMediaPlayer::NoMedia)
837 return;
838
839 if (m_state == QMediaPlayer::PausedState)
840 return;
841
843
844 if (m_videoOutput && m_videoSink)
845 m_videoOutput->setLayer([static_cast<AVFMediaPlayerObserver*>(m_observer) playerLayer]);
846
847 [[static_cast<AVFMediaPlayerObserver*>(m_observer) player] pause];
848
849 // Reset media status if the current status is EndOfMedia
850 if (m_mediaStatus == QMediaPlayer::EndOfMedia)
851 setPosition(0);
852
854 Q_EMIT stateChanged(m_state);
855 m_playbackTimer.stop();
856}
857
859{
860#ifdef QT_DEBUG_AVF
861 qDebug() << Q_FUNC_INFO << "currently: " << m_state;
862#endif
863
864 if (m_state == QMediaPlayer::StoppedState)
865 return;
866
867 // AVPlayer doesn't have stop(), only pause() and play().
868 [[static_cast<AVFMediaPlayerObserver*>(m_observer) player] pause];
869 setPosition(0);
870
871 if (m_videoOutput)
872 m_videoOutput->setLayer(nullptr);
873
874 if (m_mediaStatus == QMediaPlayer::BufferedMedia)
876
878 m_playbackTimer.stop();
879}
880
882{
883#ifdef QT_DEBUG_AVF
884 qDebug() << Q_FUNC_INFO << volume;
885#endif
886
887 AVPlayer *player = [static_cast<AVFMediaPlayerObserver*>(m_observer) player];
888 if (player)
889 player.volume = volume;
890}
891
893{
894#ifdef QT_DEBUG_AVF
895 qDebug() << Q_FUNC_INFO << muted;
896#endif
897
898 AVPlayer *player = [static_cast<AVFMediaPlayerObserver*>(m_observer) player];
899 if (player)
900 player.muted = muted;
901}
902
904{
905#ifdef Q_OS_MACOS
906 AVPlayer *player = [static_cast<AVFMediaPlayerObserver*>(m_observer) player];
908 player.audioOutputDeviceUniqueID = nil;
909 if (!m_audioOutput)
910 player.muted = true;
911 } else {
912 NSString *str = QString::fromUtf8(m_audioOutput->device.id()).toNSString();
913 player.audioOutputDeviceUniqueID = str;
914 }
915#endif
916}
917
919{
920 if (doLoop()) {
921 setPosition(0);
922 [[static_cast<AVFMediaPlayerObserver*>(m_observer) player] setRate:m_rate];
923 return;
924 }
925
926 //AVPlayerItem has reached end of track/stream
927#ifdef QT_DEBUG_AVF
928 qDebug() << Q_FUNC_INFO;
929#endif
931 m_mediaStatus = QMediaPlayer::EndOfMedia;
933
934 if (m_videoOutput)
935 m_videoOutput->setLayer(nullptr);
936
937 Q_EMIT mediaStatusChanged(m_mediaStatus);
938 Q_EMIT stateChanged(m_state);
939}
940
942{
943 AVPlayerStatus currentStatus = [[static_cast<AVFMediaPlayerObserver*>(m_observer) player] status];
944
945#ifdef QT_DEBUG_AVF
946 qDebug() << Q_FUNC_INFO << currentStatus << ", " << m_mediaStatus << ", " << newState;
947#endif
948
949 if (m_mediaStatus == QMediaPlayer::NoMedia)
950 return;
951
952 if (currentStatus == AVPlayerStatusReadyToPlay) {
953
954 QMediaPlayer::MediaStatus newStatus = m_mediaStatus;
955
956 AVPlayerItem *playerItem = [m_observer playerItem];
957
958 // get the meta data
959 m_metaData = AVFMetaData::fromAsset(playerItem.asset);
961 updateTracks();
962
963 if (playerItem) {
964 setSeekable([[playerItem seekableTimeRanges] count] > 0);
965
966 // Get the native size of the video, and reset the bounds of the player layer
967 AVPlayerLayer *playerLayer = [m_observer playerLayer];
968 if (m_observer.videoTrack && playerLayer) {
969 if (!playerLayer.bounds.size.width || !playerLayer.bounds.size.height) {
970 playerLayer.bounds = CGRectMake(0.0f, 0.0f,
971 m_observer.videoTrack.assetTrack.naturalSize.width,
972 m_observer.videoTrack.assetTrack.naturalSize.height);
973 }
974 }
975
976 if (m_requestedPosition != -1) {
977 setPosition(m_requestedPosition);
978 m_requestedPosition = -1;
979 }
980 }
981
984
985 if (newStatus != m_mediaStatus)
986 Q_EMIT mediaStatusChanged((m_mediaStatus = newStatus));
987
988 }
989
990 if (newState == QMediaPlayer::PlayingState && [static_cast<AVFMediaPlayerObserver*>(m_observer) player]) {
991 // Setting the rate is enough to start playback, no need to call play()
992 [[static_cast<AVFMediaPlayerObserver*>(m_observer) player] setRate:m_rate];
993 m_playbackTimer.start();
994 }
995}
996
997
999{
1000 processLoadStateChange(m_state);
1001}
1002
1003
1005{
1007}
1008
1010{
1011 if (bufferProgress == m_bufferProgress)
1012 return;
1013
1014 auto status = m_mediaStatus;
1015 // Buffered -> unbuffered.
1016 if (!bufferProgress) {
1018 } else if (status == QMediaPlayer::StalledMedia) {
1020 // Resume playback.
1021 if (m_state == QMediaPlayer::PlayingState) {
1022 [[static_cast<AVFMediaPlayerObserver*>(m_observer) player] setRate:m_rate];
1023 m_playbackTimer.start();
1024 }
1025 }
1026
1027 if (m_mediaStatus != status)
1028 Q_EMIT mediaStatusChanged(m_mediaStatus = status);
1029
1030 m_bufferProgress = bufferProgress;
1032}
1033
1035{
1036 if (duration == m_duration)
1037 return;
1038
1039 m_duration = duration;
1041}
1042
1044{
1045 if (m_state == QMediaPlayer::StoppedState)
1046 return;
1047
1049}
1050
1052{
1053 if (m_requestedPosition != -1) {
1054 m_requestedPosition = -1;
1056 }
1057
1059
1060 Q_EMIT error(QMediaPlayer::FormatError, tr("Failed to load media"));
1061}
1062
1064{
1065 setStreamURL(m_observer, m_resources.toEncoded());
1066}
1067
1069{
1070 resetStream(nullptr);
1071}
1072
1074{
1075 bool firstLoad = true;
1076 for (int i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
1077 if (tracks[i].count())
1078 firstLoad = false;
1079 tracks[i].clear();
1080 nativeTracks[i].clear();
1081 }
1082 AVPlayerItem *playerItem = [m_observer playerItem];
1083 if (playerItem) {
1084 // Check each track for audio and video content
1085 NSArray *tracks = playerItem.tracks;
1086 for (AVPlayerItemTrack *track in tracks) {
1087 AVAssetTrack *assetTrack = track.assetTrack;
1088 if (assetTrack) {
1089 int qtTrack = -1;
1090 if ([assetTrack.mediaType isEqualToString:AVMediaTypeAudio]) {
1092 setAudioAvailable(true);
1093 } else if ([assetTrack.mediaType isEqualToString:AVMediaTypeVideo]) {
1095 setVideoAvailable(true);
1096 if (m_observer.videoTrack != track) {
1097 m_observer.videoTrack = track;
1098 bool isMirrored = false;
1100 videoOrientationForAssetTrack(assetTrack, orientation, isMirrored);
1101 orientationChanged(orientation, isMirrored);
1102 }
1103 }
1104 else if ([assetTrack.mediaType isEqualToString:AVMediaTypeSubtitle]) {
1106 }
1107 if (qtTrack != -1) {
1109 this->tracks[qtTrack].append(metaData);
1110 nativeTracks[qtTrack].append(track);
1111 }
1112 }
1113 }
1114 // subtitles are disabled by default
1115 if (firstLoad)
1117 }
1119}
1120
1122{
1123 const auto &t = nativeTracks[type];
1125 // subtitle streams are not always automatically enabled on macOS/iOS.
1126 // this hack ensures they get enables and we actually get the text
1127 AVPlayerItem *playerItem = m_observer.m_playerItem;
1128 if (playerItem) {
1129 AVAsset *asset = playerItem.asset;
1130 if (!asset)
1131 return;
1132 AVMediaSelectionGroup *group = [asset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
1133 if (!group)
1134 return;
1135 auto *options = group.options;
1136 if (options.count)
1137 [playerItem selectMediaOption:options.firstObject inMediaSelectionGroup:group];
1138 }
1139 }
1140 for (int i = 0; i < t.count(); ++i)
1141 t.at(i).enabled = (i == index);
1143}
1144
1146{
1147 const auto &t = nativeTracks[type];
1148 for (int i = 0; i < t.count(); ++i)
1149 if (t.at(i).enabled)
1150 return i;
1151 return -1;
1152}
1153
1155{
1156 return nativeTracks[type].count();
1157}
1158
1160{
1161 const auto &t = tracks[type];
1162 if (trackNumber < 0 || trackNumber >= t.count())
1163 return QMediaMetaData();
1164 return t.at(trackNumber);
1165}
1166
1167void AVFMediaPlayer::resetStream(QIODevice *stream)
1168{
1169 if (m_mediaStream) {
1172 }
1173
1174 m_mediaStream = stream;
1175
1176 if (m_mediaStream) {
1179 }
1180}
1181
1183{
1184 if (!m_videoSink)
1185 return;
1186 m_videoSink->setNativeSize(size);
1187}
1188
1189void AVFMediaPlayer::orientationChanged(QVideoFrame::RotationAngle rotation, bool mirrored)
1190{
1191 if (!m_videoOutput)
1192 return;
1193
1194 m_videoOutput->setVideoRotation(rotation);
1195 m_videoOutput->setVideoMirrored(mirrored);
1196}
1197
1200 bool &mirrored)
1201{
1203 if (videoTrack) {
1204 CGAffineTransform transform = videoTrack.preferredTransform;
1205 if (CGAffineTransformIsIdentity(transform))
1206 return;
1207 qreal delta = transform.a * transform.d - transform.b * transform.c;
1208 qreal radians = qAtan2(transform.b, transform.a);
1209 qreal degrees = qRadiansToDegrees(radians);
1210 qreal scaleX = (transform.a/qAbs(transform.a)) * qSqrt(qPow(transform.a, 2) + qPow(transform.c, 2));
1211 qreal scaleY = (transform.d/abs(transform.d)) * qSqrt(qPow(transform.b, 2) + qPow(transform.d, 2));
1212
1213 if (delta < 0.0) { // flipped
1214 if (scaleX < 0.0) {
1215 // vertical flip
1216 degrees = -degrees;
1217 } else if (scaleY < 0.0) {
1218 // horizontal flip
1219 degrees = (180 + (int)degrees) % 360;
1220 }
1221 mirrored = true;
1222 }
1223
1224 if (qFuzzyCompare(degrees, qreal(90)) || qFuzzyCompare(degrees, qreal(-270))) {
1226 } else if (qFuzzyCompare(degrees, qreal(-90)) || qFuzzyCompare(degrees, qreal(270))) {
1228 } else if (qFuzzyCompare(degrees, qreal(180)) || qFuzzyCompare(degrees, qreal(-180))) {
1230 }
1231 }
1232}
1233
1234#include "moc_avfmediaplayer_p.cpp"
QMediaPlayer player
Definition audio.cpp:205
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)
int trackCount(TrackType) override
void setPosition(qint64 pos) override
void setVideoSink(QVideoSink *sink) override
QMediaTimeRange availablePlaybackRanges() const override
void nativeSizeChanged(QSize size)
bool isAudioAvailable() const override
QMediaMetaData metaData() const override
void setVideoOutput(AVFVideoRendererControl *output)
static void videoOrientationForAssetTrack(AVAssetTrack *track, QVideoFrame::RotationAngle &angle, bool &mirrored)
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
void processLoadStateChange(QMediaPlayer::PlaybackState newState)
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 setLayer(CALayer *layer) override
void setVideoRotation(QVideoFrame::RotationAngle)
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:106
\inmodule QtCore \reentrant
Definition qfileinfo.h:22
\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:387
void append(parameter_type t)
Definition qlist.h:441
void clear()
Definition qlist.h:417
\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.
bool isEmpty() const
Returns true if there are no intervals within the time range.
void addInterval(qint64 start, qint64 end)
This is an overloaded member function, provided for convenience. It differs from the above function o...
\inmodule QtCore
Definition qobject.h:90
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
static bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *member)
\threadsafe
Definition qobject.cpp:3099
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:127
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5857
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:208
void stop()
Stops the timer.
Definition qtimer.cpp:226
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:2814
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:2964
bool isEmpty() const
Returns true if the URL has no data; otherwise returns false.
Definition qurl.cpp:1888
RotationAngle
The angle of the clockwise rotation that should be applied to a video frame before displaying.
Definition qvideoframe.h:44
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:57
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:287
qfloat16 qSqrt(qfloat16 f)
Definition qfloat16.h:243
#define qDebug
[1]
Definition qlogging.h:160
#define qWarning
Definition qlogging.h:162
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:62
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
GLenum GLsizei len
GLdouble GLdouble t
Definition qopenglext.h:243
GLuint in
GLsizei GLenum GLboolean sink
#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:56
long long qint64
Definition qtypes.h:55
double qreal
Definition qtypes.h:92
QT_BEGIN_NAMESPACE typedef uchar * output
QUrl url("example.com")
[constructor-url-reference]
QObject::connect nullptr
myObject disconnect()
[26]
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...