AudioPlayer.m 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177
  1. #import "AudioPlayer.h"
  2. #import "AudioSource.h"
  3. #import "IndexedAudioSource.h"
  4. #import "UriAudioSource.h"
  5. #import "ConcatenatingAudioSource.h"
  6. #import "LoopingAudioSource.h"
  7. #import "ClippingAudioSource.h"
  8. #import <AVFoundation/AVFoundation.h>
  9. #import <stdlib.h>
  10. #include <TargetConditionals.h>
  11. // TODO: Check for and report invalid state transitions.
  12. // TODO: Apply Apple's guidance on seeking: https://developer.apple.com/library/archive/qa/qa1820/_index.html
  13. @implementation AudioPlayer {
  14. NSObject<FlutterPluginRegistrar>* _registrar;
  15. FlutterMethodChannel *_methodChannel;
  16. FlutterEventChannel *_eventChannel;
  17. FlutterEventSink _eventSink;
  18. NSString *_playerId;
  19. AVQueuePlayer *_player;
  20. AudioSource *_audioSource;
  21. NSMutableArray<IndexedAudioSource *> *_indexedAudioSources;
  22. NSMutableArray<NSNumber *> *_order;
  23. NSMutableArray<NSNumber *> *_orderInv;
  24. int _index;
  25. enum ProcessingState _processingState;
  26. enum LoopMode _loopMode;
  27. BOOL _shuffleModeEnabled;
  28. long long _updateTime;
  29. int _updatePosition;
  30. int _lastPosition;
  31. int _bufferedPosition;
  32. // Set when the current item hasn't been played yet so we aren't sure whether sufficient audio has been buffered.
  33. BOOL _bufferUnconfirmed;
  34. CMTime _seekPos;
  35. FlutterResult _loadResult;
  36. FlutterResult _playResult;
  37. id _timeObserver;
  38. BOOL _automaticallyWaitsToMinimizeStalling;
  39. BOOL _configuredSession;
  40. BOOL _playing;
  41. float _speed;
  42. }
  43. - (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar playerId:(NSString*)idParam configuredSession:(BOOL)configuredSession {
  44. self = [super init];
  45. NSAssert(self, @"super init cannot be nil");
  46. _registrar = registrar;
  47. _playerId = idParam;
  48. _configuredSession = configuredSession;
  49. _methodChannel =
  50. [FlutterMethodChannel methodChannelWithName:[NSMutableString stringWithFormat:@"com.ryanheise.just_audio.methods.%@", _playerId]
  51. binaryMessenger:[registrar messenger]];
  52. _eventChannel =
  53. [FlutterEventChannel eventChannelWithName:[NSMutableString stringWithFormat:@"com.ryanheise.just_audio.events.%@", _playerId]
  54. binaryMessenger:[registrar messenger]];
  55. [_eventChannel setStreamHandler:self];
  56. _index = 0;
  57. _processingState = none;
  58. _loopMode = loopOff;
  59. _shuffleModeEnabled = NO;
  60. _player = nil;
  61. _audioSource = nil;
  62. _indexedAudioSources = nil;
  63. _order = nil;
  64. _orderInv = nil;
  65. _seekPos = kCMTimeInvalid;
  66. _timeObserver = 0;
  67. _updatePosition = 0;
  68. _updateTime = 0;
  69. _lastPosition = 0;
  70. _bufferedPosition = 0;
  71. _bufferUnconfirmed = NO;
  72. _playing = NO;
  73. _loadResult = nil;
  74. _playResult = nil;
  75. _automaticallyWaitsToMinimizeStalling = YES;
  76. _speed = 1.0f;
  77. __weak __typeof__(self) weakSelf = self;
  78. [_methodChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
  79. [weakSelf handleMethodCall:call result:result];
  80. }];
  81. return self;
  82. }
  83. - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
  84. NSArray* args = (NSArray*)call.arguments;
  85. if ([@"load" isEqualToString:call.method]) {
  86. [self load:args[0] result:result];
  87. } else if ([@"play" isEqualToString:call.method]) {
  88. [self play:result];
  89. } else if ([@"pause" isEqualToString:call.method]) {
  90. [self pause];
  91. result(nil);
  92. } else if ([@"setVolume" isEqualToString:call.method]) {
  93. [self setVolume:(float)[args[0] doubleValue]];
  94. result(nil);
  95. } else if ([@"setSpeed" isEqualToString:call.method]) {
  96. [self setSpeed:(float)[args[0] doubleValue]];
  97. result(nil);
  98. } else if ([@"setLoopMode" isEqualToString:call.method]) {
  99. [self setLoopMode:[args[0] intValue]];
  100. result(nil);
  101. } else if ([@"setShuffleModeEnabled" isEqualToString:call.method]) {
  102. [self setShuffleModeEnabled:(BOOL)[args[0] boolValue]];
  103. result(nil);
  104. } else if ([@"setAutomaticallyWaitsToMinimizeStalling" isEqualToString:call.method]) {
  105. [self setAutomaticallyWaitsToMinimizeStalling:(BOOL)[args[0] boolValue]];
  106. result(nil);
  107. } else if ([@"seek" isEqualToString:call.method]) {
  108. CMTime position = args[0] == [NSNull null] ? kCMTimePositiveInfinity : CMTimeMake([args[0] intValue], 1000);
  109. [self seek:position index:args[1] completionHandler:^(BOOL finished) {
  110. result(nil);
  111. }];
  112. result(nil);
  113. } else if ([@"dispose" isEqualToString:call.method]) {
  114. [self dispose];
  115. result(nil);
  116. } else if ([@"concatenating.add" isEqualToString:call.method]) {
  117. [self concatenatingAdd:(NSString*)args[0] source:(NSDictionary*)args[1]];
  118. result(nil);
  119. } else if ([@"concatenating.insert" isEqualToString:call.method]) {
  120. [self concatenatingInsert:(NSString*)args[0] index:[args[1] intValue] source:(NSDictionary*)args[2]];
  121. result(nil);
  122. } else if ([@"concatenating.addAll" isEqualToString:call.method]) {
  123. [self concatenatingAddAll:(NSString*)args[0] sources:(NSArray*)args[1]];
  124. result(nil);
  125. } else if ([@"concatenating.insertAll" isEqualToString:call.method]) {
  126. [self concatenatingInsertAll:(NSString*)args[0] index:[args[1] intValue] sources:(NSArray*)args[2]];
  127. result(nil);
  128. } else if ([@"concatenating.removeAt" isEqualToString:call.method]) {
  129. [self concatenatingRemoveAt:(NSString*)args[0] index:[args[1] intValue]];
  130. result(nil);
  131. } else if ([@"concatenating.removeRange" isEqualToString:call.method]) {
  132. [self concatenatingRemoveRange:(NSString*)args[0] start:[args[1] intValue] end:[args[2] intValue]];
  133. result(nil);
  134. } else if ([@"concatenating.move" isEqualToString:call.method]) {
  135. [self concatenatingMove:(NSString*)args[0] currentIndex:[args[1] intValue] newIndex:[args[2] intValue]];
  136. result(nil);
  137. } else if ([@"concatenating.clear" isEqualToString:call.method]) {
  138. [self concatenatingClear:(NSString*)args[0]];
  139. result(nil);
  140. } else if ([@"setAndroidAudioAttributes" isEqualToString:call.method]) {
  141. result(nil);
  142. } else {
  143. result(FlutterMethodNotImplemented);
  144. }
  145. }
  146. // Untested
  147. - (void)concatenatingAdd:(NSString *)catId source:(NSDictionary *)source {
  148. [self concatenatingInsertAll:catId index:-1 sources:@[source]];
  149. }
  150. // Untested
  151. - (void)concatenatingInsert:(NSString *)catId index:(int)index source:(NSDictionary *)source {
  152. [self concatenatingInsertAll:catId index:index sources:@[source]];
  153. }
  154. // Untested
  155. - (void)concatenatingAddAll:(NSString *)catId sources:(NSArray *)sources {
  156. [self concatenatingInsertAll:catId index:-1 sources:sources];
  157. }
  158. // Untested
  159. - (void)concatenatingInsertAll:(NSString *)catId index:(int)index sources:(NSArray *)sources {
  160. // Find all duplicates of the identified ConcatenatingAudioSource.
  161. NSMutableArray *matches = [[NSMutableArray alloc] init];
  162. [_audioSource findById:catId matches:matches];
  163. // Add each new source to each match.
  164. for (int i = 0; i < matches.count; i++) {
  165. ConcatenatingAudioSource *catSource = (ConcatenatingAudioSource *)matches[i];
  166. int idx = index >= 0 ? index : catSource.count;
  167. NSMutableArray<AudioSource *> *audioSources = [self decodeAudioSources:sources];
  168. for (int j = 0; j < audioSources.count; j++) {
  169. AudioSource *audioSource = audioSources[j];
  170. [catSource insertSource:audioSource atIndex:(idx + j)];
  171. }
  172. }
  173. // Index the new audio sources.
  174. _indexedAudioSources = [[NSMutableArray alloc] init];
  175. [_audioSource buildSequence:_indexedAudioSources treeIndex:0];
  176. for (int i = 0; i < [_indexedAudioSources count]; i++) {
  177. IndexedAudioSource *audioSource = _indexedAudioSources[i];
  178. if (!audioSource.isAttached) {
  179. audioSource.playerItem.audioSource = audioSource;
  180. [self addItemObservers:audioSource.playerItem];
  181. }
  182. }
  183. [self updateOrder];
  184. if (_player.currentItem) {
  185. _index = [self indexForItem:_player.currentItem];
  186. } else {
  187. _index = 0;
  188. }
  189. [self enqueueFrom:_index];
  190. // Notify each new IndexedAudioSource that it's been attached to the player.
  191. for (int i = 0; i < [_indexedAudioSources count]; i++) {
  192. if (!_indexedAudioSources[i].isAttached) {
  193. [_indexedAudioSources[i] attach:_player];
  194. }
  195. }
  196. [self broadcastPlaybackEvent];
  197. }
  198. // Untested
  199. - (void)concatenatingRemoveAt:(NSString *)catId index:(int)index {
  200. [self concatenatingRemoveRange:catId start:index end:(index + 1)];
  201. }
  202. // Untested
  203. - (void)concatenatingRemoveRange:(NSString *)catId start:(int)start end:(int)end {
  204. // Find all duplicates of the identified ConcatenatingAudioSource.
  205. NSMutableArray *matches = [[NSMutableArray alloc] init];
  206. [_audioSource findById:catId matches:matches];
  207. // Remove range from each match.
  208. for (int i = 0; i < matches.count; i++) {
  209. ConcatenatingAudioSource *catSource = (ConcatenatingAudioSource *)matches[i];
  210. int endIndex = end >= 0 ? end : catSource.count;
  211. [catSource removeSourcesFromIndex:start toIndex:endIndex];
  212. }
  213. // Re-index the remaining audio sources.
  214. NSArray<IndexedAudioSource *> *oldIndexedAudioSources = _indexedAudioSources;
  215. _indexedAudioSources = [[NSMutableArray alloc] init];
  216. [_audioSource buildSequence:_indexedAudioSources treeIndex:0];
  217. for (int i = 0, j = 0; i < _indexedAudioSources.count; i++, j++) {
  218. IndexedAudioSource *audioSource = _indexedAudioSources[i];
  219. while (audioSource != oldIndexedAudioSources[j]) {
  220. [self removeItemObservers:oldIndexedAudioSources[j].playerItem];
  221. if (j < _index) {
  222. _index--;
  223. } else if (j == _index) {
  224. // The currently playing item was removed.
  225. }
  226. j++;
  227. }
  228. }
  229. [self updateOrder];
  230. if (_index >= _indexedAudioSources.count) _index = _indexedAudioSources.count - 1;
  231. if (_index < 0) _index = 0;
  232. [self enqueueFrom:_index];
  233. [self broadcastPlaybackEvent];
  234. }
  235. // Untested
  236. - (void)concatenatingMove:(NSString *)catId currentIndex:(int)currentIndex newIndex:(int)newIndex {
  237. // Find all duplicates of the identified ConcatenatingAudioSource.
  238. NSMutableArray *matches = [[NSMutableArray alloc] init];
  239. [_audioSource findById:catId matches:matches];
  240. // Move range within each match.
  241. for (int i = 0; i < matches.count; i++) {
  242. ConcatenatingAudioSource *catSource = (ConcatenatingAudioSource *)matches[i];
  243. [catSource moveSourceFromIndex:currentIndex toIndex:newIndex];
  244. }
  245. // Re-index the audio sources.
  246. _indexedAudioSources = [[NSMutableArray alloc] init];
  247. [_audioSource buildSequence:_indexedAudioSources treeIndex:0];
  248. [self updateOrder];
  249. [self enqueueFrom:[self indexForItem:_player.currentItem]];
  250. [self broadcastPlaybackEvent];
  251. }
  252. // Untested
  253. - (void)concatenatingClear:(NSString *)catId {
  254. [self concatenatingRemoveRange:catId start:0 end:-1];
  255. }
  256. - (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink {
  257. _eventSink = eventSink;
  258. return nil;
  259. }
  260. - (FlutterError*)onCancelWithArguments:(id)arguments {
  261. _eventSink = nil;
  262. return nil;
  263. }
  264. - (void)checkForDiscontinuity {
  265. if (!_eventSink) return;
  266. if (!_playing || CMTIME_IS_VALID(_seekPos) || _processingState == completed) return;
  267. int position = [self getCurrentPosition];
  268. if (_processingState == buffering) {
  269. if (position > _lastPosition) {
  270. [self leaveBuffering:@"stall ended"];
  271. [self updatePosition];
  272. [self broadcastPlaybackEvent];
  273. }
  274. } else {
  275. long long now = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
  276. long long timeSinceLastUpdate = now - _updateTime;
  277. long long expectedPosition = _updatePosition + (long long)(timeSinceLastUpdate * _player.rate);
  278. long long drift = position - expectedPosition;
  279. //NSLog(@"position: %d, drift: %lld", position, drift);
  280. // Update if we've drifted or just started observing
  281. if (_updateTime == 0L) {
  282. [self broadcastPlaybackEvent];
  283. } else if (drift < -100) {
  284. [self enterBuffering:@"stalling"];
  285. NSLog(@"Drift: %lld", drift);
  286. [self updatePosition];
  287. [self broadcastPlaybackEvent];
  288. }
  289. }
  290. _lastPosition = position;
  291. }
  292. - (void)enterBuffering:(NSString *)reason {
  293. NSLog(@"ENTER BUFFERING: %@", reason);
  294. _processingState = buffering;
  295. }
  296. - (void)leaveBuffering:(NSString *)reason {
  297. NSLog(@"LEAVE BUFFERING: %@", reason);
  298. _processingState = ready;
  299. }
  300. - (void)broadcastPlaybackEvent {
  301. if (!_eventSink) return;
  302. _eventSink(@{
  303. @"processingState": @(_processingState),
  304. @"updatePosition": @(_updatePosition),
  305. @"updateTime": @(_updateTime),
  306. // TODO: buffer position
  307. @"bufferedPosition": @(_updatePosition),
  308. // TODO: Icy Metadata
  309. @"icyMetadata": [NSNull null],
  310. @"duration": @([self getDuration]),
  311. @"currentIndex": @(_index),
  312. });
  313. }
  314. - (int)getCurrentPosition {
  315. if (_processingState == none || _processingState == loading) {
  316. return 0;
  317. } else if (CMTIME_IS_VALID(_seekPos)) {
  318. return (int)(1000 * CMTimeGetSeconds(_seekPos));
  319. } else if (_indexedAudioSources && _indexedAudioSources.count > 0) {
  320. int ms = (int)(1000 * CMTimeGetSeconds(_indexedAudioSources[_index].position));
  321. if (ms < 0) ms = 0;
  322. return ms;
  323. } else {
  324. return 0;
  325. }
  326. }
  327. - (int)getBufferedPosition {
  328. if (_processingState == none || _processingState == loading) {
  329. return 0;
  330. } else if (_indexedAudioSources && _indexedAudioSources.count > 0) {
  331. int ms = (int)(1000 * CMTimeGetSeconds(_indexedAudioSources[_index].bufferedPosition));
  332. if (ms < 0) ms = 0;
  333. return ms;
  334. } else {
  335. return 0;
  336. }
  337. }
  338. - (int)getDuration {
  339. if (_processingState == none) {
  340. return -1;
  341. } else if (_indexedAudioSources && _indexedAudioSources.count > 0) {
  342. int v = (int)(1000 * CMTimeGetSeconds(_indexedAudioSources[_index].duration));
  343. return v;
  344. } else {
  345. return 0;
  346. }
  347. }
  348. - (void)removeItemObservers:(AVPlayerItem *)playerItem {
  349. [playerItem removeObserver:self forKeyPath:@"status"];
  350. [playerItem removeObserver:self forKeyPath:@"playbackBufferEmpty"];
  351. [playerItem removeObserver:self forKeyPath:@"playbackBufferFull"];
  352. //[playerItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"];
  353. [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
  354. [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem];
  355. [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemPlaybackStalledNotification object:playerItem];
  356. }
  357. - (void)addItemObservers:(AVPlayerItem *)playerItem {
  358. // Get notified when the item is loaded or had an error loading
  359. [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
  360. // Get notified of the buffer state
  361. [playerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil];
  362. [playerItem addObserver:self forKeyPath:@"playbackBufferFull" options:NSKeyValueObservingOptionNew context:nil];
  363. [playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
  364. //[playerItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];
  365. // Get notified when playback has reached the end
  366. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onComplete:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
  367. // Get notified when playback stops due to a failure (currently unused)
  368. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onFailToComplete:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem];
  369. // Get notified when playback stalls (currently unused)
  370. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onItemStalled:) name:AVPlayerItemPlaybackStalledNotification object:playerItem];
  371. }
  372. - (NSMutableArray *)decodeAudioSources:(NSArray *)data {
  373. NSMutableArray *array = [[NSMutableArray alloc] init];
  374. for (int i = 0; i < [data count]; i++) {
  375. AudioSource *source = [self decodeAudioSource:data[i]];
  376. [array addObject:source];
  377. }
  378. return array;
  379. }
  380. - (AudioSource *)decodeAudioSource:(NSDictionary *)data {
  381. NSString *type = data[@"type"];
  382. if ([@"progressive" isEqualToString:type]) {
  383. return [[UriAudioSource alloc] initWithId:data[@"id"] uri:data[@"uri"]];
  384. } else if ([@"dash" isEqualToString:type]) {
  385. return [[UriAudioSource alloc] initWithId:data[@"id"] uri:data[@"uri"]];
  386. } else if ([@"hls" isEqualToString:type]) {
  387. return [[UriAudioSource alloc] initWithId:data[@"id"] uri:data[@"uri"]];
  388. } else if ([@"concatenating" isEqualToString:type]) {
  389. return [[ConcatenatingAudioSource alloc] initWithId:data[@"id"]
  390. audioSources:[self decodeAudioSources:data[@"audioSources"]]];
  391. } else if ([@"clipping" isEqualToString:type]) {
  392. return [[ClippingAudioSource alloc] initWithId:data[@"id"]
  393. audioSource:[self decodeAudioSource:data[@"audioSource"]]
  394. start:data[@"start"]
  395. end:data[@"end"]];
  396. } else if ([@"looping" isEqualToString:type]) {
  397. NSMutableArray *childSources = [NSMutableArray new];
  398. int count = [data[@"count"] intValue];
  399. for (int i = 0; i < count; i++) {
  400. [childSources addObject:[self decodeAudioSource:data[@"audioSource"]]];
  401. }
  402. return [[LoopingAudioSource alloc] initWithId:data[@"id"] audioSources:childSources];
  403. } else {
  404. return nil;
  405. }
  406. }
  407. - (void)enqueueFrom:(int)index {
  408. _index = index;
  409. // Update the queue while keeping the currently playing item untouched.
  410. /* NSLog(@"before reorder: _player.items.count: ", _player.items.count); */
  411. /* [self dumpQueue]; */
  412. // First, remove all _player items except for the currently playing one (if any).
  413. IndexedPlayerItem *oldItem = _player.currentItem;
  414. IndexedPlayerItem *existingItem = nil;
  415. IndexedPlayerItem *newItem = _indexedAudioSources.count > 0 ? _indexedAudioSources[_index].playerItem : nil;
  416. NSArray *oldPlayerItems = [NSArray arrayWithArray:_player.items];
  417. // In the first pass, preserve the old and new items.
  418. for (int i = 0; i < oldPlayerItems.count; i++) {
  419. if (oldPlayerItems[i] == newItem) {
  420. // Preserve and tag new item if it is already in the queue.
  421. existingItem = oldPlayerItems[i];
  422. //NSLog(@"Preserving existing item %d", [self indexForItem:existingItem]);
  423. } else if (oldPlayerItems[i] == oldItem) {
  424. //NSLog(@"Preserving old item %d", [self indexForItem:oldItem]);
  425. // Temporarily preserve old item, just to avoid jumping to
  426. // intermediate queue positions unnecessarily. We only want to jump
  427. // once to _index.
  428. } else {
  429. //NSLog(@"Removing item %d", [self indexForItem:oldPlayerItems[i]]);
  430. [_player removeItem:oldPlayerItems[i]];
  431. }
  432. }
  433. // In the second pass, remove the old item (if different from new item).
  434. if (oldItem && newItem != oldItem) {
  435. //NSLog(@"removing old item %d", [self indexForItem:oldItem]);
  436. [_player removeItem:oldItem];
  437. }
  438. /* NSLog(@"inter order: _player.items.count: ", _player.items.count); */
  439. /* [self dumpQueue]; */
  440. // Regenerate queue
  441. BOOL include = NO;
  442. for (int i = 0; i < [_order count]; i++) {
  443. int si = [_order[i] intValue];
  444. if (si == _index) include = YES;
  445. if (include && _indexedAudioSources[si].playerItem != existingItem) {
  446. //NSLog(@"inserting item %d", si);
  447. [_player insertItem:_indexedAudioSources[si].playerItem afterItem:nil];
  448. }
  449. }
  450. /* NSLog(@"after reorder: _player.items.count: ", _player.items.count); */
  451. /* [self dumpQueue]; */
  452. if (_processingState != loading && oldItem != newItem) {
  453. // || !_player.currentItem.playbackLikelyToKeepUp;
  454. if (_player.currentItem.playbackBufferEmpty) {
  455. [self enterBuffering:@"enqueueFrom playbackBufferEmpty"];
  456. } else {
  457. [self leaveBuffering:@"enqueueFrom !playbackBufferEmpty"];
  458. }
  459. [self updatePosition];
  460. }
  461. [self updateEndAction];
  462. }
  463. - (void)updatePosition {
  464. _updatePosition = [self getCurrentPosition];
  465. _updateTime = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
  466. }
  467. - (void)load:(NSDictionary *)source result:(FlutterResult)result {
  468. if (!_playing) {
  469. [_player pause];
  470. }
  471. if (_processingState == loading) {
  472. [self abortExistingConnection];
  473. }
  474. _loadResult = result;
  475. _index = 0;
  476. [self updatePosition];
  477. _processingState = loading;
  478. [self broadcastPlaybackEvent];
  479. // Remove previous observers
  480. if (_indexedAudioSources) {
  481. for (int i = 0; i < [_indexedAudioSources count]; i++) {
  482. [self removeItemObservers:_indexedAudioSources[i].playerItem];
  483. }
  484. }
  485. // Decode audio source
  486. if (_audioSource && [@"clipping" isEqualToString:source[@"type"]]) {
  487. // Check if we're clipping an audio source that was previously loaded.
  488. UriAudioSource *child = nil;
  489. if ([_audioSource isKindOfClass:[ClippingAudioSource class]]) {
  490. ClippingAudioSource *clipper = (ClippingAudioSource *)_audioSource;
  491. child = clipper.audioSource;
  492. } else if ([_audioSource isKindOfClass:[UriAudioSource class]]) {
  493. child = (UriAudioSource *)_audioSource;
  494. }
  495. if (child) {
  496. _audioSource = [[ClippingAudioSource alloc] initWithId:source[@"id"]
  497. audioSource:child
  498. start:source[@"start"]
  499. end:source[@"end"]];
  500. } else {
  501. _audioSource = [self decodeAudioSource:source];
  502. }
  503. } else {
  504. _audioSource = [self decodeAudioSource:source];
  505. }
  506. _indexedAudioSources = [[NSMutableArray alloc] init];
  507. [_audioSource buildSequence:_indexedAudioSources treeIndex:0];
  508. for (int i = 0; i < [_indexedAudioSources count]; i++) {
  509. IndexedAudioSource *source = _indexedAudioSources[i];
  510. [self addItemObservers:source.playerItem];
  511. source.playerItem.audioSource = source;
  512. }
  513. [self updateOrder];
  514. // Set up an empty player
  515. if (!_player) {
  516. _player = [[AVQueuePlayer alloc] initWithItems:@[]];
  517. if (@available(macOS 10.12, iOS 10.0, *)) {
  518. _player.automaticallyWaitsToMinimizeStalling = _automaticallyWaitsToMinimizeStalling;
  519. // TODO: Remove these observers in dispose.
  520. [_player addObserver:self
  521. forKeyPath:@"timeControlStatus"
  522. options:NSKeyValueObservingOptionNew
  523. context:nil];
  524. }
  525. [_player addObserver:self
  526. forKeyPath:@"currentItem"
  527. options:NSKeyValueObservingOptionNew
  528. context:nil];
  529. // TODO: learn about the different ways to define weakSelf.
  530. //__weak __typeof__(self) weakSelf = self;
  531. //typeof(self) __weak weakSelf = self;
  532. __unsafe_unretained typeof(self) weakSelf = self;
  533. if (@available(macOS 10.12, iOS 10.0, *)) {}
  534. else {
  535. _timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMake(200, 1000)
  536. queue:nil
  537. usingBlock:^(CMTime time) {
  538. [weakSelf checkForDiscontinuity];
  539. }
  540. ];
  541. }
  542. }
  543. // Initialise the AVQueuePlayer with items.
  544. [self enqueueFrom:0];
  545. // Notify each IndexedAudioSource that it's been attached to the player.
  546. for (int i = 0; i < [_indexedAudioSources count]; i++) {
  547. [_indexedAudioSources[i] attach:_player];
  548. }
  549. if (_player.currentItem.status == AVPlayerItemStatusReadyToPlay) {
  550. _loadResult(@([self getDuration]));
  551. _loadResult = nil;
  552. } else {
  553. // We send result after the playerItem is ready in observeValueForKeyPath.
  554. }
  555. [self broadcastPlaybackEvent];
  556. }
  557. - (void)updateOrder {
  558. if (_shuffleModeEnabled) {
  559. [_audioSource shuffle:0 currentIndex: _index];
  560. }
  561. _orderInv = [NSMutableArray arrayWithCapacity:[_indexedAudioSources count]];
  562. for (int i = 0; i < [_indexedAudioSources count]; i++) {
  563. [_orderInv addObject:@(0)];
  564. }
  565. if (_shuffleModeEnabled) {
  566. _order = [_audioSource getShuffleOrder];
  567. } else {
  568. NSMutableArray *order = [[NSMutableArray alloc] init];
  569. for (int i = 0; i < [_indexedAudioSources count]; i++) {
  570. [order addObject:@(i)];
  571. }
  572. _order = order;
  573. }
  574. for (int i = 0; i < [_indexedAudioSources count]; i++) {
  575. _orderInv[[_order[i] intValue]] = @(i);
  576. }
  577. }
  578. - (void)onItemStalled:(NSNotification *)notification {
  579. IndexedPlayerItem *playerItem = (IndexedPlayerItem *)notification.object;
  580. NSLog(@"onItemStalled");
  581. }
  582. - (void)onFailToComplete:(NSNotification *)notification {
  583. IndexedPlayerItem *playerItem = (IndexedPlayerItem *)notification.object;
  584. NSLog(@"onFailToComplete");
  585. }
  586. - (void)onComplete:(NSNotification *)notification {
  587. NSLog(@"onComplete");
  588. if (_loopMode == loopOne) {
  589. [self seek:kCMTimeZero index:@(_index) completionHandler:^(BOOL finished) {
  590. // XXX: Not necessary?
  591. [self play];
  592. }];
  593. } else {
  594. IndexedPlayerItem *endedPlayerItem = (IndexedPlayerItem *)notification.object;
  595. IndexedAudioSource *endedSource = endedPlayerItem.audioSource;
  596. if ([_orderInv[_index] intValue] + 1 < [_order count]) {
  597. // When an item ends, seek back to its beginning.
  598. [endedSource seek:kCMTimeZero];
  599. // account for automatic move to next item
  600. _index = [_order[[_orderInv[_index] intValue] + 1] intValue];
  601. NSLog(@"advance to next: index = %d", _index);
  602. [self updateEndAction];
  603. [self broadcastPlaybackEvent];
  604. } else {
  605. // reached end of playlist
  606. if (_loopMode == loopAll) {
  607. NSLog(@"Loop back to first item");
  608. // Loop back to the beginning
  609. // TODO: Currently there will be a gap at the loop point.
  610. // Maybe we can do something clever by temporarily adding the
  611. // first playlist item at the end of the queue, although this
  612. // will affect any code that assumes the queue always
  613. // corresponds to a contiguous region of the indexed audio
  614. // sources.
  615. // For now we just do a seek back to the start.
  616. if ([_order count] == 1) {
  617. [self seek:kCMTimeZero index:_order[0] completionHandler:^(BOOL finished) {
  618. // XXX: Necessary?
  619. [self play];
  620. }];
  621. } else {
  622. // When an item ends, seek back to its beginning.
  623. [endedSource seek:kCMTimeZero];
  624. [self seek:kCMTimeZero index:_order[0] completionHandler:^(BOOL finished) {
  625. // XXX: Necessary?
  626. [self play];
  627. }];
  628. }
  629. } else {
  630. [self complete];
  631. }
  632. }
  633. }
  634. }
  635. - (void)observeValueForKeyPath:(NSString *)keyPath
  636. ofObject:(id)object
  637. change:(NSDictionary<NSString *,id> *)change
  638. context:(void *)context {
  639. if ([keyPath isEqualToString:@"status"]) {
  640. IndexedPlayerItem *playerItem = (IndexedPlayerItem *)object;
  641. AVPlayerItemStatus status = AVPlayerItemStatusUnknown;
  642. NSNumber *statusNumber = change[NSKeyValueChangeNewKey];
  643. if ([statusNumber isKindOfClass:[NSNumber class]]) {
  644. status = statusNumber.intValue;
  645. }
  646. switch (status) {
  647. case AVPlayerItemStatusReadyToPlay: {
  648. if (playerItem != _player.currentItem) return;
  649. // Detect buffering in different ways depending on whether we're playing
  650. if (_playing) {
  651. if (@available(macOS 10.12, iOS 10.0, *)) {
  652. if (_player.timeControlStatus == AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate) {
  653. [self enterBuffering:@"ready to play: playing, waitingToPlay"];
  654. } else {
  655. [self leaveBuffering:@"ready to play: playing, !waitingToPlay"];
  656. }
  657. [self updatePosition];
  658. } else {
  659. // If this happens when we're playing, check whether buffer is confirmed
  660. if (_bufferUnconfirmed && !_player.currentItem.playbackBufferFull) {
  661. // Stay in bufering - XXX Test
  662. [self enterBuffering:@"ready to play: playing, bufferUnconfirmed && !playbackBufferFull"];
  663. } else {
  664. if (_player.currentItem.playbackBufferEmpty) {
  665. // !_player.currentItem.playbackLikelyToKeepUp;
  666. [self enterBuffering:@"ready to play: playing, playbackBufferEmpty"];
  667. } else {
  668. [self leaveBuffering:@"ready to play: playing, !playbackBufferEmpty"];
  669. }
  670. [self updatePosition];
  671. }
  672. }
  673. } else {
  674. if (_player.currentItem.playbackBufferEmpty) {
  675. [self enterBuffering:@"ready to play: !playing, playbackBufferEmpty"];
  676. // || !_player.currentItem.playbackLikelyToKeepUp;
  677. } else {
  678. [self leaveBuffering:@"ready to play: !playing, !playbackBufferEmpty"];
  679. }
  680. [self updatePosition];
  681. }
  682. [self broadcastPlaybackEvent];
  683. if (_loadResult) {
  684. _loadResult(@([self getDuration]));
  685. _loadResult = nil;
  686. }
  687. break;
  688. }
  689. case AVPlayerItemStatusFailed: {
  690. NSLog(@"AVPlayerItemStatusFailed");
  691. [self sendErrorForItem:playerItem];
  692. break;
  693. }
  694. case AVPlayerItemStatusUnknown:
  695. break;
  696. }
  697. } else if ([keyPath isEqualToString:@"playbackBufferEmpty"] || [keyPath isEqualToString:@"playbackBufferFull"]) {
  698. // Use these values to detect buffering.
  699. IndexedPlayerItem *playerItem = (IndexedPlayerItem *)object;
  700. if (playerItem != _player.currentItem) return;
  701. // If there's a seek in progress, these values are unreliable
  702. if (CMTIME_IS_VALID(_seekPos)) return;
  703. // Detect buffering in different ways depending on whether we're playing
  704. if (_playing) {
  705. if (@available(macOS 10.12, iOS 10.0, *)) {
  706. // We handle this with timeControlStatus instead.
  707. } else {
  708. if (_bufferUnconfirmed && playerItem.playbackBufferFull) {
  709. _bufferUnconfirmed = NO;
  710. [self leaveBuffering:@"playing, _bufferUnconfirmed && playbackBufferFull"];
  711. [self updatePosition];
  712. NSLog(@"Buffering confirmed! leaving buffering");
  713. [self broadcastPlaybackEvent];
  714. }
  715. }
  716. } else {
  717. if (playerItem.playbackBufferEmpty) {
  718. [self enterBuffering:@"!playing, playbackBufferEmpty"];
  719. [self updatePosition];
  720. [self broadcastPlaybackEvent];
  721. } else if (!playerItem.playbackBufferEmpty || playerItem.playbackBufferFull) {
  722. _processingState = ready;
  723. [self leaveBuffering:@"!playing, !playbackBufferEmpty || playbackBufferFull"];
  724. [self updatePosition];
  725. [self broadcastPlaybackEvent];
  726. }
  727. }
  728. /* } else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) { */
  729. } else if ([keyPath isEqualToString:@"timeControlStatus"]) {
  730. if (@available(macOS 10.12, iOS 10.0, *)) {
  731. AVPlayerTimeControlStatus status = AVPlayerTimeControlStatusPaused;
  732. NSNumber *statusNumber = change[NSKeyValueChangeNewKey];
  733. if ([statusNumber isKindOfClass:[NSNumber class]]) {
  734. status = statusNumber.intValue;
  735. }
  736. switch (status) {
  737. case AVPlayerTimeControlStatusPaused:
  738. //NSLog(@"AVPlayerTimeControlStatusPaused");
  739. break;
  740. case AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate:
  741. //NSLog(@"AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate");
  742. if (_processingState != completed) {
  743. [self enterBuffering:@"timeControlStatus"];
  744. [self updatePosition];
  745. [self broadcastPlaybackEvent];
  746. } else {
  747. NSLog(@"Ignoring wait signal because we reached the end");
  748. }
  749. break;
  750. case AVPlayerTimeControlStatusPlaying:
  751. [self leaveBuffering:@"timeControlStatus"];
  752. [self updatePosition];
  753. [self broadcastPlaybackEvent];
  754. break;
  755. }
  756. }
  757. } else if ([keyPath isEqualToString:@"currentItem"] && _player.currentItem) {
  758. if (_player.currentItem.status == AVPlayerItemStatusFailed) {
  759. if ([_orderInv[_index] intValue] + 1 < [_order count]) {
  760. // account for automatic move to next item
  761. _index = [_order[[_orderInv[_index] intValue] + 1] intValue];
  762. NSLog(@"advance to next on error: index = %d", _index);
  763. [self updateEndAction];
  764. [self broadcastPlaybackEvent];
  765. } else {
  766. NSLog(@"error on last item");
  767. }
  768. return;
  769. } else {
  770. int expectedIndex = [self indexForItem:_player.currentItem];
  771. if (_index != expectedIndex) {
  772. // AVQueuePlayer will sometimes skip over error items without
  773. // notifying this observer.
  774. NSLog(@"Queue change detected. Adjusting index from %d -> %d", _index, expectedIndex);
  775. _index = expectedIndex;
  776. [self updateEndAction];
  777. [self broadcastPlaybackEvent];
  778. }
  779. }
  780. //NSLog(@"currentItem changed. _index=%d", _index);
  781. _bufferUnconfirmed = YES;
  782. // If we've skipped or transitioned to a new item and we're not
  783. // currently in the middle of a seek
  784. if (CMTIME_IS_INVALID(_seekPos) && _player.currentItem.status == AVPlayerItemStatusReadyToPlay) {
  785. [self updatePosition];
  786. IndexedAudioSource *source = ((IndexedPlayerItem *)_player.currentItem).audioSource;
  787. // We should already be at position zero but for
  788. // ClippingAudioSource it might be off by some milliseconds so we
  789. // consider anything <= 100 as close enough.
  790. if ((int)(1000 * CMTimeGetSeconds(source.position)) > 100) {
  791. NSLog(@"On currentItem change, seeking back to zero");
  792. BOOL shouldResumePlayback = NO;
  793. AVPlayerActionAtItemEnd originalEndAction = _player.actionAtItemEnd;
  794. if (_playing && CMTimeGetSeconds(CMTimeSubtract(source.position, source.duration)) >= 0) {
  795. NSLog(@"Need to pause while rewinding because we're at the end");
  796. shouldResumePlayback = YES;
  797. _player.actionAtItemEnd = AVPlayerActionAtItemEndPause;
  798. [_player pause];
  799. }
  800. [self enterBuffering:@"currentItem changed, seeking"];
  801. [self updatePosition];
  802. [self broadcastPlaybackEvent];
  803. [source seek:kCMTimeZero completionHandler:^(BOOL finished) {
  804. [self leaveBuffering:@"currentItem changed, finished seek"];
  805. [self updatePosition];
  806. [self broadcastPlaybackEvent];
  807. if (shouldResumePlayback) {
  808. _player.actionAtItemEnd = originalEndAction;
  809. // TODO: This logic is almost duplicated in seek. See if we can reuse this code.
  810. _player.rate = _speed;
  811. }
  812. }];
  813. } else {
  814. // Already at zero, no need to seek.
  815. }
  816. }
  817. } else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
  818. IndexedPlayerItem *playerItem = (IndexedPlayerItem *)object;
  819. if (playerItem != _player.currentItem) return;
  820. int pos = [self getBufferedPosition];
  821. if (pos != _bufferedPosition) {
  822. _bufferedPosition = pos;
  823. [self broadcastPlaybackEvent];
  824. }
  825. }
  826. }
  827. - (void)sendErrorForItem:(IndexedPlayerItem *)playerItem {
  828. FlutterError *flutterError = [FlutterError errorWithCode:[NSString stringWithFormat:@"%d", playerItem.error.code]
  829. message:playerItem.error.localizedDescription
  830. details:nil];
  831. [self sendError:flutterError playerItem:playerItem];
  832. }
  833. - (void)sendError:(FlutterError *)flutterError playerItem:(IndexedPlayerItem *)playerItem {
  834. NSLog(@"sendError");
  835. if (_loadResult && playerItem == _player.currentItem) {
  836. _loadResult(flutterError);
  837. _loadResult = nil;
  838. }
  839. if (_eventSink) {
  840. // Broadcast all errors even if they aren't on the current item.
  841. _eventSink(flutterError);
  842. }
  843. }
  844. - (void)abortExistingConnection {
  845. FlutterError *flutterError = [FlutterError errorWithCode:@"abort"
  846. message:@"Connection aborted"
  847. details:nil];
  848. [self sendError:flutterError playerItem:nil];
  849. }
  850. - (int)indexForItem:(IndexedPlayerItem *)playerItem {
  851. for (int i = 0; i < _indexedAudioSources.count; i++) {
  852. if (_indexedAudioSources[i].playerItem == playerItem) {
  853. return i;
  854. }
  855. }
  856. return -1;
  857. }
  858. - (void)play {
  859. [self play:nil];
  860. }
  861. - (void)play:(FlutterResult)result {
  862. if (result) {
  863. if (_playResult) {
  864. NSLog(@"INTERRUPTING PLAY");
  865. _playResult(nil);
  866. }
  867. _playResult = result;
  868. }
  869. _playing = YES;
  870. #if TARGET_OS_IPHONE
  871. if (_configuredSession) {
  872. [[AVAudioSession sharedInstance] setActive:YES error:nil];
  873. }
  874. #endif
  875. _player.rate = _speed;
  876. [self updatePosition];
  877. if (@available(macOS 10.12, iOS 10.0, *)) {}
  878. else {
  879. if (_bufferUnconfirmed && !_player.currentItem.playbackBufferFull) {
  880. [self enterBuffering:@"play, _bufferUnconfirmed && !playbackBufferFull"];
  881. [self broadcastPlaybackEvent];
  882. }
  883. }
  884. }
  885. - (void)pause {
  886. _playing = NO;
  887. [_player pause];
  888. [self updatePosition];
  889. [self broadcastPlaybackEvent];
  890. if (_playResult) {
  891. NSLog(@"PLAY FINISHED DUE TO PAUSE");
  892. _playResult(nil);
  893. _playResult = nil;
  894. }
  895. }
  896. - (void)complete {
  897. [self updatePosition];
  898. _processingState = completed;
  899. [self broadcastPlaybackEvent];
  900. if (_playResult) {
  901. NSLog(@"PLAY FINISHED DUE TO COMPLETE");
  902. _playResult(nil);
  903. _playResult = nil;
  904. }
  905. }
  906. - (void)setVolume:(float)volume {
  907. [_player setVolume:volume];
  908. }
  909. - (void)setSpeed:(float)speed {
  910. if (speed == 1.0
  911. || (speed < 1.0 && _player.currentItem.canPlaySlowForward)
  912. || (speed > 1.0 && _player.currentItem.canPlayFastForward)) {
  913. _speed = speed;
  914. if (_playing) {
  915. _player.rate = speed;
  916. }
  917. }
  918. [self updatePosition];
  919. }
  920. - (void)setLoopMode:(int)loopMode {
  921. _loopMode = loopMode;
  922. [self updateEndAction];
  923. }
  924. - (void)updateEndAction {
  925. // Should update this whenever the audio source changes and whenever _index changes.
  926. if (!_player) return;
  927. if (_audioSource && [_orderInv[_index] intValue] + 1 < [_order count] && _loopMode != loopOne) {
  928. _player.actionAtItemEnd = AVPlayerActionAtItemEndAdvance;
  929. } else {
  930. _player.actionAtItemEnd = AVPlayerActionAtItemEndPause; // AVPlayerActionAtItemEndNone
  931. }
  932. }
  933. - (void)setShuffleModeEnabled:(BOOL)shuffleModeEnabled {
  934. NSLog(@"setShuffleModeEnabled: %d", shuffleModeEnabled);
  935. _shuffleModeEnabled = shuffleModeEnabled;
  936. if (!_audioSource) return;
  937. [self updateOrder];
  938. [self enqueueFrom:_index];
  939. }
  940. - (void)dumpQueue {
  941. for (int i = 0; i < _player.items.count; i++) {
  942. IndexedPlayerItem *playerItem = _player.items[i];
  943. for (int j = 0; j < _indexedAudioSources.count; j++) {
  944. IndexedAudioSource *source = _indexedAudioSources[j];
  945. if (source.playerItem == playerItem) {
  946. NSLog(@"- %d", j);
  947. break;
  948. }
  949. }
  950. }
  951. }
  952. - (void)setAutomaticallyWaitsToMinimizeStalling:(bool)automaticallyWaitsToMinimizeStalling {
  953. _automaticallyWaitsToMinimizeStalling = automaticallyWaitsToMinimizeStalling;
  954. if (@available(macOS 10.12, iOS 10.0, *)) {
  955. if(_player) {
  956. _player.automaticallyWaitsToMinimizeStalling = automaticallyWaitsToMinimizeStalling;
  957. }
  958. }
  959. }
  960. - (void)seek:(CMTime)position index:(NSNumber *)newIndex completionHandler:(void (^)(BOOL))completionHandler {
  961. int index = _index;
  962. if (newIndex != [NSNull null]) {
  963. index = [newIndex intValue];
  964. }
  965. if (index != _index) {
  966. // Jump to a new item
  967. /* if (_playing && index == _index + 1) { */
  968. /* // Special case for jumping to the very next item */
  969. /* NSLog(@"seek to next item: %d -> %d", _index, index); */
  970. /* [_indexedAudioSources[_index] seek:kCMTimeZero]; */
  971. /* _index = index; */
  972. /* [_player advanceToNextItem]; */
  973. /* [self broadcastPlaybackEvent]; */
  974. /* } else */
  975. {
  976. // Jump to a distant item
  977. //NSLog(@"seek# jump to distant item: %d -> %d", _index, index);
  978. if (_playing) {
  979. [_player pause];
  980. }
  981. [_indexedAudioSources[_index] seek:kCMTimeZero];
  982. // The "currentItem" key observer will respect that a seek is already in progress
  983. _seekPos = position;
  984. [self updatePosition];
  985. [self enqueueFrom:index];
  986. IndexedAudioSource *source = _indexedAudioSources[_index];
  987. if (abs((int)(1000 * CMTimeGetSeconds(CMTimeSubtract(source.position, position)))) > 100) {
  988. [self enterBuffering:@"seek to index"];
  989. [self updatePosition];
  990. [self broadcastPlaybackEvent];
  991. [source seek:position completionHandler:^(BOOL finished) {
  992. if (@available(macOS 10.12, iOS 10.0, *)) {
  993. if (_playing) {
  994. // Handled by timeControlStatus
  995. } else {
  996. if (_bufferUnconfirmed && !_player.currentItem.playbackBufferFull) {
  997. // Stay in buffering
  998. } else if (source.playerItem.status == AVPlayerItemStatusReadyToPlay) {
  999. [self leaveBuffering:@"seek to index finished, (!bufferUnconfirmed || playbackBufferFull) && ready to play"];
  1000. [self updatePosition];
  1001. [self broadcastPlaybackEvent];
  1002. }
  1003. }
  1004. } else {
  1005. if (_bufferUnconfirmed && !_player.currentItem.playbackBufferFull) {
  1006. // Stay in buffering
  1007. } else if (source.playerItem.status == AVPlayerItemStatusReadyToPlay) {
  1008. [self leaveBuffering:@"seek to index finished, (!bufferUnconfirmed || playbackBufferFull) && ready to play"];
  1009. [self updatePosition];
  1010. [self broadcastPlaybackEvent];
  1011. }
  1012. }
  1013. if (_playing) {
  1014. _player.rate = _speed;
  1015. }
  1016. _seekPos = kCMTimeInvalid;
  1017. [self broadcastPlaybackEvent];
  1018. if (completionHandler) {
  1019. completionHandler(finished);
  1020. }
  1021. }];
  1022. } else {
  1023. _seekPos = kCMTimeInvalid;
  1024. if (_playing) {
  1025. if (@available(iOS 10.0, *)) {
  1026. // NOTE: Re-enable this line only after figuring out
  1027. // how to detect buffering when buffered audio is not
  1028. // immediately available.
  1029. //[_player playImmediatelyAtRate:_speed];
  1030. _player.rate = _speed;
  1031. } else {
  1032. _player.rate = _speed;
  1033. }
  1034. }
  1035. }
  1036. }
  1037. } else {
  1038. // Seek within an item
  1039. if (_playing) {
  1040. [_player pause];
  1041. }
  1042. _seekPos = position;
  1043. //NSLog(@"seek. enter buffering. pos = %d", (int)(1000*CMTimeGetSeconds(_indexedAudioSources[_index].position)));
  1044. // TODO: Move this into a separate method so it can also
  1045. // be used in skip.
  1046. [self enterBuffering:@"seek"];
  1047. [self updatePosition];
  1048. [self broadcastPlaybackEvent];
  1049. [_indexedAudioSources[_index] seek:position completionHandler:^(BOOL finished) {
  1050. [self updatePosition];
  1051. if (_playing) {
  1052. // If playing, buffering will be detected either by:
  1053. // 1. checkForDiscontinuity
  1054. // 2. timeControlStatus
  1055. if (@available(iOS 10.0, *)) {
  1056. // NOTE: Re-enable this line only after figuring out how to
  1057. // detect buffering when buffered audio is not immediately
  1058. // available.
  1059. //[_player playImmediatelyAtRate:_speed];
  1060. _player.rate = _speed;
  1061. } else {
  1062. _player.rate = _speed;
  1063. }
  1064. } else {
  1065. // If not playing, there is no reliable way to detect
  1066. // when buffering has completed, so we use
  1067. // !playbackBufferEmpty. Although this always seems to
  1068. // be full even right after a seek.
  1069. if (_player.currentItem.playbackBufferEmpty) {
  1070. [self enterBuffering:@"seek finished, playbackBufferEmpty"];
  1071. } else {
  1072. [self leaveBuffering:@"seek finished, !playbackBufferEmpty"];
  1073. }
  1074. [self updatePosition];
  1075. if (_processingState != buffering) {
  1076. [self broadcastPlaybackEvent];
  1077. }
  1078. }
  1079. _seekPos = kCMTimeInvalid;
  1080. [self broadcastPlaybackEvent];
  1081. if (completionHandler) {
  1082. completionHandler(finished);
  1083. }
  1084. }];
  1085. }
  1086. }
  1087. - (void)dispose {
  1088. if (_processingState != none) {
  1089. [_player pause];
  1090. _processingState = none;
  1091. [self broadcastPlaybackEvent];
  1092. }
  1093. if (_timeObserver) {
  1094. [_player removeTimeObserver:_timeObserver];
  1095. _timeObserver = 0;
  1096. }
  1097. if (_indexedAudioSources) {
  1098. for (int i = 0; i < [_indexedAudioSources count]; i++) {
  1099. [self removeItemObservers:_indexedAudioSources[i].playerItem];
  1100. }
  1101. }
  1102. if (_player) {
  1103. [_player removeObserver:self forKeyPath:@"currentItem"];
  1104. if (@available(macOS 10.12, iOS 10.0, *)) {
  1105. [_player removeObserver:self forKeyPath:@"timeControlStatus"];
  1106. }
  1107. _player = nil;
  1108. }
  1109. // Untested:
  1110. // [_eventChannel setStreamHandler:nil];
  1111. // [_methodChannel setMethodHandler:nil];
  1112. }
  1113. @end