From 4ee33d3bd92134e3480fbb8799e57d05e263e367 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Nov 2014 21:26:39 +0300 Subject: [PATCH] added os x window icon and qt os x build fix to git --- Telegram/SourceFiles/art/iconbig128.png | Bin 0 -> 7007 bytes .../mediaplayer/avfmediaplayersession.mm | 851 ++++++++++++++++++ 2 files changed, 851 insertions(+) create mode 100644 Telegram/SourceFiles/art/iconbig128.png create mode 100644 Telegram/_qt_5_3_1_patch/qtmultimedia/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.mm diff --git a/Telegram/SourceFiles/art/iconbig128.png b/Telegram/SourceFiles/art/iconbig128.png new file mode 100644 index 0000000000000000000000000000000000000000..32b44a5eaee7a8f74a4d85568c086c33a1e884fe GIT binary patch literal 7007 zcmaJ`XEw&x?WIMm>CS}9Qbe)rUU>G2*NEOD2SnfqLYu8 z81#>i7}^VQjRpXeRM7~ilLrjN=?HU$dnxPM=p$KtFF^NAV{q1OI z`2V|ldHu&5i86!zH{bu07-Q{@q0r zHy@M_(#;3Ksj2x_*Ej_Yp-yn`KZ`c)Xt|2R<1y+}lmi`+H_Hpv_f_bC<#ybBOEAx-oKZ5W=Tt^1O zeBu5uXKh~}FV4RTSA_p_E|UMq_iwE8Kj$L#k67{RWW@h$@BiBFzn88x^k@1X@m?GM zkw47)TJyfw;CUn8-7U)mg4~SE3j{>i{ThCj%L<52a7Ig(mGfp~MD8^?LkYkV4WgX5L5TX$iZJqiK*|NH=o$EHJ$x zWmgF2g!PE}9py9~xLXR(#fOZ}(uncdYPr=-oobQ3=JMp~(l|{`%{3BoP-N=OiJ9_| zP+D|Sk6nq|<;BfL&3X|CCgIiaEEqv_cMVIvb!6?@N4X|iW7CN^ZBV;&ov+nTqjp)L z{W9h%;~OMYH(O}JOa^6SYk?8f2R1TH-s4wFi5_TnU8lM5$3E8^=F|7;6 zAXg$YbGe^Bw^XFe`dvK<-od(e(T1Jb4G`YW9EzOX*ch&bjM+@ZA3z5@C=KpDb{_OQ zip3;=(R62hdIE5n+mN(N+yEoR31L)P-k=DUTee-W;mm_+qME+sI4|r_jyoWKOO>M5KE(VDik<;ljt8?JdOdZ=3i_W~%f3}^5Md{Pv(Zd?3Kd3b!%ECcXf z=~9PyZ~0D1#qM(BY}SG>zJ|p1>WL?oZpD30;~&;^qT1KuO4s55)fAJhS@zavg?)u0 zUH}i$NlkkfnEqz;xd0VJmaqoUydjCq&bhC3iT~?+=?+8)e|Ic@qGey0KV1JbY!j%K zhkqFObE4trr$;%r1JsKi)VPz8q*Hf+Q(m>GRB?CpER*2w&FJA7-+bG2WMX+{{|zZZ zClB!6S#nDWpLr3pi*@!>xK99~?Ckd7!*v5wEXM%o{z36=$=3V@?NfI?;IeCj>ru^Z zD(ZEHNqn*gC1N;X|E1REU66o_Ftc2>2b`-=he1zj6~21Grsc; z>p=0?@}ybgCq$RswQup+3YAp!bGP+fBvZ$*eDF3@3yTLs>IwsKgCMAxutA}sSLgjQ zi%(0&^AW@dY=StX2j8bMJhJ)RO@p%rr06>iC_$Qp=(EGR{2m{^uly1!G_-qCtRgX0>i$tusmG?Q1$_ zydG_BT*FM=bSs)a*3K+Msz58@OJv@Y)96-v1>@*pp%k*c=#jJ@TB2`$9=!MEX==Bj z%P9$x5%ons^^7yfsZ5?G4@_4eb=#`?Fh>c$A&tGUfnhK+y~%8hvaa(Hkvd=Iq%S?r z9h+A!6(R0Grk_~W$txHZply!Ga4OW{3Y(d-9@|o}mAl7QhN2i`d*l*d`5n_OR7J%* zVy30K^h*Qiu(-MY1Cy_u*RNzbH2ZcRDk~IvhOc^2_dI$!XW?mRVetU`0{8w>ChAz2 z0T0qvJ?<*`LWefe`ZCTl|4eUkb=zVvxZ;+*+%QJA^pJ&~n9!J``Weg4E7o-Ho8>`u zOT3Omse_|}-SyL^x@!saH*mh+%JY($@WhJA-E%7ST-SJ|a!0n{`^QKRp@G&L4VP~? z#R^I&$)|JheLFQj(tTmkf8Hsc-0_S-eI|Na=<*m8c=gZ`&#t_4im3Nx5H~@aKmYBb zqxWp%oJN)xN-I8Fx!o3n#0b&AiyC~;Ib>+ydHz;3!Uu@!5oE#u%S#%XL{e@MxFx{e zTiDYT9}PZsj~I7}Z*97*FmNK^_cn}+4mB%YGw28Y^5jE%!pb&JcOl;`!{UT46=q}X zhM&{rHs=DER3M&peQPI$H;w1lFp|i6Eq254o6UrAvJ9YgZya8G$e8tQp+Xn-5+wa? zp!Vyg{H@T{mYS8PtgiA%`Hi5Tq^M)?dj@?@{g39W2g`}~)q}h?Gwb127sTA8@$|m3 znUgD$hZoqp1j|jo=nZV%8Z9lc>6i5JI0a@9%Zu$h@mcO6^?T>rXZy#NP@Hs!pVpZp2$Y3Jd@Wne!k|y_82v_?A%|(h;OsVs2lBo`+d zd|Dswh=^C)8j^#s+uxqZ zwl$W`#~z8wKIIV1Z(`uKKgkiJhd+FUUOo3ei5s8n59xg@?H z#xLK#89WU*_7_-2-r1CGcBeYJc+RXwGe%`mU6rvJ-c96sVj6cP5_oRCvhAU}xAB2a zeo}?0-Z;%ZbZJh$ec5@-nDMYf&rXVWng_)HZbCT;wON!beluoGI_wWYrTld!L-q5(V+9}>lbZuL(QlF`p9C_hUS4U{T%qUp2&quwkFc1cBq+$^qsE05{%-;VLc&x#qSfDHkUKQ%w z)Dcq6h@M@=GBx2Bd*zR2z1;;9OQhe-G^4t*Yz6eJR(%n{ANQ{jMh57JVPfmy1BUwU z`eXCtm9psFvu?$pqZrB)TH11@_>ywnGxd6wgSJ<~`g}(rJ(NZYT_iHgzUv%~_Y9$n z!Eb0yp0F@9K60v*xjcJ0j~O$*S8)QBL;a$G-`-D*p``PRcWU03-V||%?+``yn~Me6o+Uj?Czn@5znYLGzo0X}G||+dp(tQ4_Fot=%}soT2Cd8R|<5J@LQM=Mg;}dsc5Pq6FaX|^f8l5s@eOY%4@%U8A`ah`J~*|9;kT_ za(B$eK1!8>J9OFZU@^kxB;LTvRQK*UGs)wX_aHk(rHH_tSI~JSjyi8jM?NM^^LNT_ zz;d(1%_GU%%WdJRoXm>p-`11PACGR05^Dw2Tlr;`cHWC?_7@mb0obR`0@tq_svG}a zvWX-yQ-0>#Spqc~#kU#wnR!q;gq_Z!pkgm5O0s#- z9PPLRD4e6~Q*Z{_=X3NXIRhCcO5Bz8U#EbD@V^d~`twAWU_Mv2`gh0MO0H4SM;w|4 zp!g|xUK!wxObxxr7{&iotqmp;H7?=`NG<yQ@rOC|2oEFDkc|yd0BXGcm>=CYnMB8$m>Fu<{+|HXeYrrqrn0HC6|+ zzqTKNr*m06ZdOq_+8EOZV|8!Pwgc8&aVH(%xd2PuGm*}Y6_8?TA6MDTJM2C7s4bD8FiKV9+9W3T$!|H^JOh$7xQumKO{-PVYM|mu<^WpV2;v zo2Qh~$rD|{hnUKQ!jdz%vBwCqH1rdV4lrDe1>^5tJXL?N<#8fqxgV0z=eZNdtp>l# zVo7?>^64=LYdjzq@v6YQBhVs;OU@CUKgXuKl^>KS)2ogBu#XNfxTle&(=MMj__pqb zMXTqlJtZ*ct^G#IicUit!b9EwhqBSZk5^Kp_$KTX+3L^UFU-M4O$^HMH>3FSd6AAL zPi@g?yr89NcKhxcOK+@IwVCz{yu$Kw{mTeo#Ov&#<;AX;Y~i1w0^DfaZ!2nwITBmC zg~u?;mEnP_{?U9vjoJ?c{DD3a?#UHo0gyXHcXvDT=Vl7%ZE9wy;c7JsrKOYIm#LFU z?8T|#J_>POv3`$AEJjFZ`=tW>hSJiU-$yn-?UH_i*WlUMe5u>0-bxcoEZDmL>z66f zvw+`k4gx|gq_02r!LtuNmx93C$2e7?6ZMJo#t$K`ph)X_$5#!^{2@{;cy8$NvFK-l z*65^2eq4V`)Le5~y+QKJ`R#W>!k2|MyD{rM^8@UOdH(Fw1Fy6%h{t+jc>j3J$-rciCgqi|H}@s-bbs5@hg99ilF= zL#+%nY@Evz{^RBmraDo3_n{3f0leG0agMP^IZ=TkOB7#+bf<3xs^+Kg?o$!<=Nf#> zOhFG)UY7gz*Mu>lNN(&;jMwSeom$YRjGNjf$)6m?NAY-NOSpLDBCTC6>}DhmeQHuJ zyJj|@=`~mJr1c9t&_yRv=G_R5hm=M?q|ZiXdRP>Yxbj}Cu?RF=H2y&UP;+ij6VZ}E z#CxqV`%_20rJE)DBOTT;Yl%WBjeY?X;eJiX#*BgAk8;CLxE?OIaR@ zsFB6?4D{6!?^+Zy3P!hZ@j+$bUg?KlP zZ!WzGx+k%06-T=J&;$OS{z;-7LK_@`8{6Qqb?JnK%;gMV&Tlay(1To56n<;aL*u4$8wyaEd0utu6ljgu`N6%geMQuL-bgP-`MygkcnEetT->1IUl1+8n!*+HI8uEqGwvAb05{Ke2(w5=g=b^EJe&ib{cDW#OSNtK8{^c zB?V5bDz&HVwhiVz0{@^5(NOq}RyC&7<-Tarxc`d%Hf12s_vzS`Ig|`}y*=IUh`mg# zUyxHR)g`m)&!BR)t-`s-9}%{P!1(#`_IhD4{%VWXX}uCZ$C?Y(G`xn3-cPz}uW?h6 zJ&7RyiOJ7x?c{ee6R6PZ{ao^FiL3K+6n)G^J-os14uv74~j(XPb0`7v5DR#hJ zp5H&y776ASNIwu;N`^n9JI&4A>(F2SSgd2Vn`O`5dt%3?N4m9PU-}%&V-1uW|mWgK5p(c})r7yZQf>1gBz9R%rl^>OI68u>7a$D!bm{z4t z(s|xysHx;gJHzCaj2KX%vGdO3x_s_rgkR<|$m$_b$?{2&|M4nN9I>-c*tUlM8_b3* zcH-tJtj0$qy6;|u`2dUG-_`Nx~3OVaOZAT=-B2^-oP>uE#32o zR^BoBtStnnp{V=zRF>(KL&&L`fAON@#YCrmHpL` zn(ED$uW5;TH%MA;g@7isb|k+Jj5_96Sm!rl@gmAA=<0P2nO=SrynL;NKD98uomtb8 zvG)ZU>JA~@uXKGoFMHeferR6%w>u_L%Q0pjnS)hTdG$lvQUwa*3BMM(A~~SKo=mKY zIka_xWVSbA9V>?>e?-5GGG9~5*~nIITH>dx<-{7)iLx)-(5gA>6OZ z(p58&xb^d?*A837D_9!k5SBR_^DUQ5?bDX8KA#mUD{8p6bG|2JR)r4g;)sHl^KRKY zY?WFC1{I4}7#p<##eWB10cJ_DKbWL?d3*)~A!nqvdXf zS&ntOXF9F^0^pdjO)YQrm><;Bj)^yBZhgh4v}7So`P7m367f8MX$_5fPoI)m70v{n6FbmjOyR782(4g0dc`fH{Cpve9xsi0NN=@L0b3 z>!udl5b?Qy&G0H2LtwVmlxfw&keQRDuH(Ae9d(@OXt*|nd=>a5#}gppqt?F`wR7uN zZO?hdn^Qs@lF{|#r-3ZM{Acu%;^oftyREY}`ohswOC_CZKWZybU#f9!&jZY{7IylK zm~rA=cqd7?Mq8B1$@mO=h%g)U%myn;bQI_t6Le8@ODrl3KY`CGeT<$}&^v6wy)-5^ z5g)EN?h%zoy1kd8wv%qH?MzO!Kv^Q?qIt@{PYEg{@_o9y6O}+J{JiKbbTngjV5yf+ zb@5(t_B7em(@!Hl`rJg?MP;50KjUZ|rcRvIFZ9j%*l++$$iZdA|HkAv z0nV`JOS6ngG{?$ z;x}-{%+La+ajTNYgZ8b4@Lf>{z>`n!L4-I&=WuUX*Dsg4NTQeZ_gAPZxIR#yhV9*7 zZ4W8dWDer!xrc7$rL>apDI1J?JKz!(2WZLf*}uXE0@Uq&G}K7bgZ}*T($z8o*J(IB F`X6{u + +QT_USE_NAMESPACE + +//AVAsset Keys +static NSString* const AVF_TRACKS_KEY = @"tracks"; +static NSString* const AVF_PLAYABLE_KEY = @"playable"; + +//AVPlayerItem keys +static NSString* const AVF_STATUS_KEY = @"status"; + +//AVPlayer keys +static NSString* const AVF_RATE_KEY = @"rate"; +static NSString* const AVF_CURRENT_ITEM_KEY = @"currentItem"; + +static void *AVFMediaPlayerSessionObserverRateObservationContext = &AVFMediaPlayerSessionObserverRateObservationContext; +static void *AVFMediaPlayerSessionObserverStatusObservationContext = &AVFMediaPlayerSessionObserverStatusObservationContext; +static void *AVFMediaPlayerSessionObserverCurrentItemObservationContext = &AVFMediaPlayerSessionObserverCurrentItemObservationContext; + +@interface AVFMediaPlayerSessionObserver : NSObject +{ +@private + AVFMediaPlayerSession *m_session; + AVPlayer *m_player; + AVPlayerItem *m_playerItem; + AVPlayerLayer *m_playerLayer; + NSURL *m_URL; + bool m_audioAvailable; + bool m_videoAvailable; +} + +@property (readonly, getter=player) AVPlayer* m_player; +@property (readonly, getter=playerItem) AVPlayerItem* m_playerItem; +@property (readonly, getter=playerLayer) AVPlayerLayer* m_playerLayer; +@property (readonly, getter=audioAvailable) bool m_audioAvailable; +@property (readonly, getter=videoAvailable) bool m_videoAvailable; +@property (readonly, getter=session) AVFMediaPlayerSession* m_session; + +- (AVFMediaPlayerSessionObserver *) initWithMediaPlayerSession:(AVFMediaPlayerSession *)session; +- (void) setURL:(NSURL *)url; +- (void) unloadMedia; +- (void) prepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys; +- (void) assetFailedToPrepareForPlayback:(NSError *)error; +- (void) playerItemDidReachEnd:(NSNotification *)notification; +- (void) playerItemTimeJumped:(NSNotification *)notification; +- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object + change:(NSDictionary *)change context:(void *)context; +- (void) detatchSession; +- (void) dealloc; +@end + +@implementation AVFMediaPlayerSessionObserver + +@synthesize m_player, m_playerItem, m_playerLayer, m_audioAvailable, m_videoAvailable, m_session; + +- (AVFMediaPlayerSessionObserver *) initWithMediaPlayerSession:(AVFMediaPlayerSession *)session +{ + if (!(self = [super init])) + return nil; + + self->m_session = session; + return self; +} + +- (void) setURL:(NSURL *)url +{ + if (m_URL != url) + { + [m_URL release]; + m_URL = [url copy]; + + //Create an asset for inspection of a resource referenced by a given URL. + //Load the values for the asset keys "tracks", "playable". + + AVURLAsset *asset = [AVURLAsset URLAssetWithURL:m_URL options:nil]; + NSArray *requestedKeys = [NSArray arrayWithObjects:AVF_TRACKS_KEY, AVF_PLAYABLE_KEY, nil]; + + // Tells the asset to load the values of any of the specified keys that are not already loaded. + [asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler: + ^{ + dispatch_async( dispatch_get_main_queue(), + ^{ + [self prepareToPlayAsset:asset withKeys:requestedKeys]; + }); + }]; + } +} + +- (void) unloadMedia +{ + if (m_player) + [m_player setRate:0.0]; + if (m_playerItem) { + [m_playerItem removeObserver:self forKeyPath:AVF_STATUS_KEY]; + + [[NSNotificationCenter defaultCenter] removeObserver:self + name:AVPlayerItemDidPlayToEndTimeNotification + object:m_playerItem]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:AVPlayerItemTimeJumpedNotification + object:m_playerItem]; + m_playerItem = 0; + } +} + +- (void) prepareToPlayAsset:(AVURLAsset *)asset + withKeys:(NSArray *)requestedKeys +{ + //Make sure that the value of each key has loaded successfully. + for (NSString *thisKey in requestedKeys) + { + NSError *error = nil; + AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error]; +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << [thisKey UTF8String] << " status: " << keyStatus; +#endif + if (keyStatus == AVKeyValueStatusFailed) + { + [self assetFailedToPrepareForPlayback:error]; + return; + } + } + + //Use the AVAsset playable property to detect whether the asset can be played. +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << "isPlayable: " << [asset isPlayable]; +#endif + if (!asset.playable) + { + //Generate an error describing the failure. + NSString *localizedDescription = NSLocalizedString(@"Item cannot be played", @"Item cannot be played description"); + NSString *localizedFailureReason = NSLocalizedString(@"The assets tracks were loaded, but could not be made playable.", @"Item cannot be played failure reason"); + NSDictionary *errorDict = [NSDictionary dictionaryWithObjectsAndKeys: + localizedDescription, NSLocalizedDescriptionKey, + localizedFailureReason, NSLocalizedFailureReasonErrorKey, + nil]; + NSError *assetCannotBePlayedError = [NSError errorWithDomain:@"StitchedStreamPlayer" code:0 userInfo:errorDict]; + + [self assetFailedToPrepareForPlayback:assetCannotBePlayedError]; + + return; + } + + m_audioAvailable = false; + m_videoAvailable = false; + + //Check each track of asset for audio and video content + NSArray *tracks = [asset tracks]; + for (AVAssetTrack *track in tracks) { + if ([track hasMediaCharacteristic:AVMediaCharacteristicAudible]) + m_audioAvailable = true; + if ([track hasMediaCharacteristic:AVMediaCharacteristicVisual]) + m_videoAvailable = true; + } + + //At this point we're ready to set up for playback of the asset. + //Stop observing our prior AVPlayerItem, if we have one. + if (m_playerItem) + { + //Remove existing player item key value observers and notifications. + [self unloadMedia]; + } + + //Create a new instance of AVPlayerItem from the now successfully loaded AVAsset. + m_playerItem = [AVPlayerItem playerItemWithAsset:asset]; + + //Observe the player item "status" key to determine when it is ready to play. + [m_playerItem addObserver:self + forKeyPath:AVF_STATUS_KEY + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:AVFMediaPlayerSessionObserverStatusObservationContext]; + + //When the player item has played to its end time we'll toggle + //the movie controller Pause button to be the Play button + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(playerItemDidReachEnd:) + name:AVPlayerItemDidPlayToEndTimeNotification + object:m_playerItem]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(playerItemTimeJumped:) + name:AVPlayerItemTimeJumpedNotification + object:m_playerItem]; + + + //Clean up old player if we have one + if (m_player) { + [m_player setRate:0.0]; + [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_KEY]; + [m_player removeObserver:self forKeyPath:AVF_RATE_KEY]; + [m_player release]; + m_player = 0; + + if (m_playerLayer) { + [m_playerLayer release]; + m_playerLayer = 0; //Will have been released + } + } + + //Get a new AVPlayer initialized to play the specified player item. + m_player = [AVPlayer playerWithPlayerItem:m_playerItem]; + [m_player retain]; + +#if defined(Q_OS_OSX) + //Set the initial volume on new player object + if (self.session) + m_player.volume = m_session->volume() / 100.0f; +#endif + + //Create a new player layer if we don't have one already + if (!m_playerLayer) + { + m_playerLayer = [AVPlayerLayer playerLayerWithPlayer:m_player]; + [m_playerLayer retain]; + m_playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; + + //Get the native size of the new item, and reset the bounds of the player layer + AVAsset *asset = m_playerItem.asset; + if (asset) { + NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo]; + if ([tracks count]) { + AVAssetTrack *videoTrack = [tracks objectAtIndex:0]; + m_playerLayer.anchorPoint = CGPointMake(0.0f, 0.0f); + m_playerLayer.bounds = CGRectMake(0.0f, 0.0f, videoTrack.naturalSize.width, videoTrack.naturalSize.height); + } + } + + } + + //Observe the AVPlayer "currentItem" property to find out when any + //AVPlayer replaceCurrentItemWithPlayerItem: replacement will/did + //occur. + [m_player addObserver:self + forKeyPath:AVF_CURRENT_ITEM_KEY + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:AVFMediaPlayerSessionObserverCurrentItemObservationContext]; + + //Observe the AVPlayer "rate" property to update the scrubber control. + [m_player addObserver:self + forKeyPath:AVF_RATE_KEY + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:AVFMediaPlayerSessionObserverRateObservationContext]; + +} + +-(void) assetFailedToPrepareForPlayback:(NSError *)error +{ + Q_UNUSED(error) + QMetaObject::invokeMethod(m_session, "processMediaLoadError", Qt::AutoConnection); +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; + qDebug() << [[error localizedDescription] UTF8String]; + qDebug() << [[error localizedFailureReason] UTF8String]; + qDebug() << [[error localizedRecoverySuggestion] UTF8String]; +#endif +} + +- (void) playerItemDidReachEnd:(NSNotification *)notification +{ + Q_UNUSED(notification) + if (self.session) + QMetaObject::invokeMethod(m_session, "processEOS", Qt::AutoConnection); +} + +- (void) playerItemTimeJumped:(NSNotification *)notification +{ + Q_UNUSED(notification) + if (self.session) + QMetaObject::invokeMethod(m_session, "processPositionChange", Qt::AutoConnection); +} + +- (void) observeValueForKeyPath:(NSString*) path + ofObject:(id)object + change:(NSDictionary*)change + context:(void*)context +{ + //AVPlayerItem "status" property value observer. + if (context == AVFMediaPlayerSessionObserverStatusObservationContext) + { + AVPlayerStatus status = (AVPlayerStatus)[[change objectForKey:NSKeyValueChangeNewKey] integerValue]; + switch (status) + { + //Indicates that the status of the player is not yet known because + //it has not tried to load new media resources for playback + case AVPlayerStatusUnknown: + { + //QMetaObject::invokeMethod(m_session, "processLoadStateChange", Qt::AutoConnection); + } + break; + + case AVPlayerStatusReadyToPlay: + { + //Once the AVPlayerItem becomes ready to play, i.e. + //[playerItem status] == AVPlayerItemStatusReadyToPlay, + //its duration can be fetched from the item. + if (self.session) + QMetaObject::invokeMethod(m_session, "processLoadStateChange", Qt::AutoConnection); + } + break; + + case AVPlayerStatusFailed: + { + AVPlayerItem *playerItem = (AVPlayerItem *)object; + [self assetFailedToPrepareForPlayback:playerItem.error]; + + if (self.session) + QMetaObject::invokeMethod(m_session, "processLoadStateChange", Qt::AutoConnection); + } + break; + } + } + //AVPlayer "rate" property value observer. + else if (context == AVFMediaPlayerSessionObserverRateObservationContext) + { + //QMetaObject::invokeMethod(m_session, "setPlaybackRate", Qt::AutoConnection, Q_ARG(qreal, [m_player rate])); + } + //AVPlayer "currentItem" property observer. + //Called when the AVPlayer replaceCurrentItemWithPlayerItem: + //replacement will/did occur. + else if (context == AVFMediaPlayerSessionObserverCurrentItemObservationContext) + { + AVPlayerItem *newPlayerItem = [change objectForKey:NSKeyValueChangeNewKey]; + if (m_playerItem != newPlayerItem) + { + m_playerItem = newPlayerItem; + + //Get the native size of the new item, and reset the bounds of the player layer + //AVAsset *asset = m_playerItem.asset; + AVAsset *asset = [m_playerItem asset]; + if (asset) { + NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo]; + if ([tracks count]) { + AVAssetTrack *videoTrack = [tracks objectAtIndex:0]; + m_playerLayer.anchorPoint = CGPointMake(0.0f, 0.0f); + m_playerLayer.bounds = CGRectMake(0.0f, 0.0f, videoTrack.naturalSize.width, videoTrack.naturalSize.height); + } + } + + } + if (self.session) + QMetaObject::invokeMethod(m_session, "processCurrentItemChanged", Qt::AutoConnection); + } + else + { + [super observeValueForKeyPath:path ofObject:object change:change context:context]; + } +} + +- (void) detatchSession +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + m_session = 0; +} + +- (void) dealloc +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + [self unloadMedia]; + + if (m_player) { + [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_KEY]; + [m_player removeObserver:self forKeyPath:AVF_RATE_KEY]; + [m_player release]; + m_player = 0; + } + + if (m_playerLayer) { + [m_playerLayer release]; + m_playerLayer = 0; + } + + if (m_URL) { + [m_URL release]; + } + + [super dealloc]; +} + +@end + +AVFMediaPlayerSession::AVFMediaPlayerSession(AVFMediaPlayerService *service, QObject *parent) + : QObject(parent) + , m_service(service) + , m_videoOutput(0) + , m_state(QMediaPlayer::StoppedState) + , m_mediaStatus(QMediaPlayer::NoMedia) + , m_mediaStream(0) + , m_muted(false) + , m_tryingAsync(false) + , m_volume(100) + , m_rate(1.0) + , m_duration(0) + , m_videoAvailable(false) + , m_audioAvailable(false) +{ + m_observer = [[AVFMediaPlayerSessionObserver alloc] initWithMediaPlayerSession:this]; +} + +AVFMediaPlayerSession::~AVFMediaPlayerSession() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + //Detatch the session from the sessionObserver (which could still be alive trying to communicate with this session). + [(AVFMediaPlayerSessionObserver*)m_observer detatchSession]; + [(AVFMediaPlayerSessionObserver*)m_observer release]; +} + +void AVFMediaPlayerSession::setVideoOutput(AVFVideoOutput *output) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << output; +#endif + + if (m_videoOutput == output) + return; + + //Set the current output layer to null to stop rendering + if (m_videoOutput) { + m_videoOutput->setLayer(0); + } + + m_videoOutput = output; + + if (m_videoOutput && m_state != QMediaPlayer::StoppedState) + m_videoOutput->setLayer([(AVFMediaPlayerSessionObserver*)m_observer playerLayer]); +} + +void *AVFMediaPlayerSession::currentAssetHandle() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + AVAsset *currentAsset = [[(AVFMediaPlayerSessionObserver*)m_observer playerItem] asset]; + return currentAsset; +} + +QMediaPlayer::State AVFMediaPlayerSession::state() const +{ + return m_state; +} + +QMediaPlayer::MediaStatus AVFMediaPlayerSession::mediaStatus() const +{ + return m_mediaStatus; +} + +QMediaContent AVFMediaPlayerSession::media() const +{ + return m_resources; +} + +const QIODevice *AVFMediaPlayerSession::mediaStream() const +{ + return m_mediaStream; +} + +void AVFMediaPlayerSession::setMedia(const QMediaContent &content, QIODevice *stream) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << content.canonicalUrl(); +#endif + + m_resources = content; + m_mediaStream = stream; + + QMediaPlayer::MediaStatus oldMediaStatus = m_mediaStatus; + + if (content.isNull() || content.canonicalUrl().isEmpty()) { + [(AVFMediaPlayerSessionObserver*)m_observer unloadMedia]; + m_mediaStatus = QMediaPlayer::NoMedia; + if (m_state != QMediaPlayer::StoppedState) + Q_EMIT stateChanged(m_state = QMediaPlayer::StoppedState); + + if (m_mediaStatus != oldMediaStatus) + Q_EMIT mediaStatusChanged(m_mediaStatus); + Q_EMIT positionChanged(position()); + return; + } else { + + m_mediaStatus = QMediaPlayer::LoadingMedia; + if (m_mediaStatus != oldMediaStatus) + Q_EMIT mediaStatusChanged(m_mediaStatus); + } + //Load AVURLAsset + //initialize asset using content's URL + NSString *urlString = [NSString stringWithUTF8String:content.canonicalUrl().toEncoded().constData()]; + NSURL *url = [NSURL URLWithString:urlString]; + [(AVFMediaPlayerSessionObserver*)m_observer setURL:url]; +} + +qint64 AVFMediaPlayerSession::position() const +{ + AVPlayerItem *playerItem = [(AVFMediaPlayerSessionObserver*)m_observer playerItem]; + + if (!playerItem) + return 0; + + CMTime time = [playerItem currentTime]; + return static_cast(float(time.value) / float(time.timescale) * 1000.0f); +} + +qint64 AVFMediaPlayerSession::duration() const +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + AVPlayerItem *playerItem = [(AVFMediaPlayerSessionObserver*)m_observer playerItem]; + + if (!playerItem) + return 0; + + CMTime time = [playerItem duration]; + return static_cast(float(time.value) / float(time.timescale) * 1000.0f); +} + +int AVFMediaPlayerSession::bufferStatus() const +{ + //BUG: bufferStatus may be relevant? +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + return 100; +} + +int AVFMediaPlayerSession::volume() const +{ + return m_volume; +} + +bool AVFMediaPlayerSession::isMuted() const +{ + return m_muted; +} + +bool AVFMediaPlayerSession::isAudioAvailable() const +{ + return [(AVFMediaPlayerSessionObserver*)m_observer audioAvailable]; +} + +bool AVFMediaPlayerSession::isVideoAvailable() const +{ + return [(AVFMediaPlayerSessionObserver*)m_observer videoAvailable]; +} + +bool AVFMediaPlayerSession::isSeekable() const +{ + return true; +} + +QMediaTimeRange AVFMediaPlayerSession::availablePlaybackRanges() const +{ + AVPlayerItem *playerItem = [(AVFMediaPlayerSessionObserver*)m_observer playerItem]; + + if (playerItem) { + QMediaTimeRange timeRanges; + + NSArray *ranges = [playerItem loadedTimeRanges]; + for (NSValue *timeRange in ranges) { + CMTimeRange currentTimeRange = [timeRange CMTimeRangeValue]; + qint64 startTime = qint64(float(currentTimeRange.start.value) / currentTimeRange.start.timescale * 1000.0); + timeRanges.addInterval(startTime, startTime + qint64(float(currentTimeRange.duration.value) / currentTimeRange.duration.timescale * 1000.0)); + } + if (!timeRanges.isEmpty()) + return timeRanges; + } + return QMediaTimeRange(0, duration()); +} + +qreal AVFMediaPlayerSession::playbackRate() const +{ + return m_rate; +} + +void AVFMediaPlayerSession::setPlaybackRate(qreal rate) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << rate; +#endif + + if (qFuzzyCompare(m_rate, rate)) + return; + + m_rate = rate; + + AVPlayer *player = [(AVFMediaPlayerSessionObserver*)m_observer player]; + + if (player != 0 && m_state == QMediaPlayer::PlayingState) { + [player setRate:m_rate]; + } +} + +void AVFMediaPlayerSession::setPosition(qint64 pos) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << pos; +#endif + + if ( !isSeekable() || pos == position()) + return; + + AVPlayerItem *playerItem = [(AVFMediaPlayerSessionObserver*)m_observer playerItem]; + + if (!playerItem) + return; + + if (duration() > 0) + pos = qMin(pos, duration()); + + CMTime newTime = [playerItem currentTime]; + newTime.value = (pos / 1000.0f) * newTime.timescale; + [playerItem seekToTime:newTime]; + + //reset the EndOfMedia status position is changed after playback is finished + if (m_mediaStatus == QMediaPlayer::EndOfMedia) + processLoadStateChange(); +} + +void AVFMediaPlayerSession::play() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << "currently: " << m_state; +#endif + + if (m_state == QMediaPlayer::PlayingState) + return; + + m_state = QMediaPlayer::PlayingState; + + if (m_videoOutput) { + m_videoOutput->setLayer([(AVFMediaPlayerSessionObserver*)m_observer playerLayer]); + } + + //reset the EndOfMedia status if the same file is played again + if (m_mediaStatus == QMediaPlayer::EndOfMedia) { + setPosition(0); + processLoadStateChange(); + } + + if (m_mediaStatus == QMediaPlayer::LoadedMedia || m_mediaStatus == QMediaPlayer::BufferedMedia) + [[(AVFMediaPlayerSessionObserver*)m_observer player] play]; + + //processLoadStateChange(); + Q_EMIT stateChanged(m_state); +} + +void AVFMediaPlayerSession::pause() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << "currently: " << m_state; +#endif + + if (m_state == QMediaPlayer::PausedState) + return; + + m_state = QMediaPlayer::PausedState; + + if (m_videoOutput) { + m_videoOutput->setLayer([(AVFMediaPlayerSessionObserver*)m_observer playerLayer]); + } + + //reset the EndOfMedia status if the same file is played again + if (m_mediaStatus == QMediaPlayer::EndOfMedia) + processLoadStateChange(); + + [[(AVFMediaPlayerSessionObserver*)m_observer player] pause]; + + //processLoadStateChange(); + Q_EMIT stateChanged(m_state); +} + +void AVFMediaPlayerSession::stop() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << "currently: " << m_state; +#endif + + if (m_state == QMediaPlayer::StoppedState) + return; + + m_state = QMediaPlayer::StoppedState; + m_rate = 0.0f; + [[(AVFMediaPlayerSessionObserver*)m_observer player] setRate:m_rate]; + setPosition(0); + + if (m_videoOutput) { + m_videoOutput->setLayer(0); + } + + processLoadStateChange(); + Q_EMIT stateChanged(m_state); + Q_EMIT positionChanged(position()); +} + +void AVFMediaPlayerSession::setVolume(int volume) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << volume; +#endif + + if (m_volume == volume) + return; + + m_volume = volume; + +#if defined(Q_OS_OSX) + AVPlayer *player = [(AVFMediaPlayerSessionObserver*)m_observer player]; + if (player) { + [[(AVFMediaPlayerSessionObserver*)m_observer player] setVolume:m_volume / 100.0f]; + } +#endif + + Q_EMIT volumeChanged(m_volume); +} + +void AVFMediaPlayerSession::setMuted(bool muted) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << muted; +#endif + if (m_muted == muted) + return; + + m_muted = muted; +#if defined(Q_OS_OSX) + [[(AVFMediaPlayerSessionObserver*)m_observer player] setMuted:m_muted]; +#endif + Q_EMIT mutedChanged(muted); +} + +void AVFMediaPlayerSession::processEOS() +{ + //AVPlayerItem has reached end of track/stream +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + Q_EMIT positionChanged(position()); + m_mediaStatus = QMediaPlayer::EndOfMedia; + + Q_EMIT stateChanged(m_state = QMediaPlayer::StoppedState); + Q_EMIT mediaStatusChanged(m_mediaStatus); +} + +void AVFMediaPlayerSession::processLoadStateChange() +{ + AVPlayerStatus currentStatus = [[(AVFMediaPlayerSessionObserver*)m_observer player] status]; + +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << currentStatus; +#endif + + QMediaPlayer::MediaStatus newStatus = QMediaPlayer::NoMedia; + bool isPlaying = (m_state != QMediaPlayer::StoppedState); + + if (currentStatus == AVPlayerStatusReadyToPlay) { + qint64 currentDuration = duration(); + if (m_duration != currentDuration) + Q_EMIT durationChanged(m_duration = currentDuration); + + if (m_audioAvailable != isAudioAvailable()) + Q_EMIT audioAvailableChanged(m_audioAvailable = !m_audioAvailable); + + if (m_videoAvailable != isVideoAvailable()) + Q_EMIT videoAvailableChanged(m_videoAvailable = !m_videoAvailable); + + newStatus = isPlaying ? QMediaPlayer::BufferedMedia : QMediaPlayer::LoadedMedia; + + if (m_state == QMediaPlayer::PlayingState && [(AVFMediaPlayerSessionObserver*)m_observer player]) { + [[(AVFMediaPlayerSessionObserver*)m_observer player] setRate:m_rate]; + [[(AVFMediaPlayerSessionObserver*)m_observer player] play]; + } + } + + if (newStatus != m_mediaStatus) + Q_EMIT mediaStatusChanged(m_mediaStatus = newStatus); +} + +void AVFMediaPlayerSession::processPositionChange() +{ + Q_EMIT positionChanged(position()); +} + +void AVFMediaPlayerSession::processMediaLoadError() +{ + Q_EMIT error(QMediaPlayer::FormatError, tr("Failed to load media")); + Q_EMIT mediaStatusChanged(m_mediaStatus = QMediaPlayer::InvalidMedia); + Q_EMIT stateChanged(m_state = QMediaPlayer::StoppedState); +} + +void AVFMediaPlayerSession::processCurrentItemChanged() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + + AVPlayerLayer *playerLayer = [(AVFMediaPlayerSessionObserver*)m_observer playerLayer]; + + if (m_videoOutput && m_state != QMediaPlayer::StoppedState) { + m_videoOutput->setLayer(playerLayer); + } + +}