1
0
mirror of https://github.com/danog/Telegram.git synced 2024-12-02 09:27:55 +01:00
Telegram/Telegraph/TGMusicPlayer.m
2016-02-25 01:03:51 +01:00

1130 lines
44 KiB
Objective-C

#import "TGMusicPlayer.h"
#import <AVFoundation/AVFoundation.h>
#import <MediaPlayer/MediaPlayer.h>
#import "TGPreparedLocalDocumentMessage.h"
#import "TGMessage.h"
#import "TGTimerTarget.h"
#import "TGObserverProxy.h"
#import "TGImageUtils.h"
#import "TGMusicPlayerPlaylist.h"
#import "TGAudioSessionManager.h"
#import "TGRemoteControlsManager.h"
#import "TGMusicPlayerItemSignals.h"
#import "TGModernConversationAudioPlayer.h"
#import "TGAppDelegate.h"
static TGMusicPlayerDownloadingStatus TGMusicPlayerDownloadingStatusMake(bool downloaded, bool downloading, CGFloat progress)
{
return (TGMusicPlayerDownloadingStatus){.downloaded = downloaded, .downloading = downloading, .progress = progress};
}
@interface TGMusicPlayerStatus ()
@end
@implementation TGMusicPlayerStatus
- (instancetype)initWithItem:(TGMusicPlayerItem *)item position:(TGMusicPlayerItemPosition)position paused:(bool)paused offset:(CGFloat)offset duration:(CGFloat)duration albumArt:(SSignal *)albumArt albumArtSync:(SSignal *)albumArtSync downloadedStatus:(TGMusicPlayerDownloadingStatus)downloadedStatus isVoice:(bool)isVoice shuffle:(bool)shuffle repeatType:(TGMusicPlayerRepeatType)repeatType
{
self = [super init];
if (self != nil)
{
_item = item;
_position = position;
_paused = paused;
_offset = offset;
_duration = duration;
_timestamp = CACurrentMediaTime();
_albumArt = albumArt;
_albumArtSync = albumArtSync;
_downloadedStatus = downloadedStatus;
_isVoice = isVoice;
_shuffle = shuffle;
_repeatType = repeatType;
}
return self;
}
@end
@interface TGMusicPlayer () <TGModernConversationAudioPlayerDelegate>
{
bool _initialized;
SQueue *_queue;
TGModernConversationAudioPlayer *_player;
STimer *_updateTimer;
SPipe *_playingStatusPipe;
SPipe *_playlistFinishedPipe;
TGMusicPlayerStatus *_currentStatus;
SMetaDisposable *_currentItemDisposable;
TGMusicPlayerItem *_currentNextItem;
SMetaDisposable *_nextItemDisposable;
SMetaDisposable *_currentAudioSession;
SMetaDisposable *_currentRemoteControls;
SMetaDisposable *_currentAlbumArtDisposable;
SMulticastSignalManager *_albumArtMulticastManager;
SMetaDisposable *_currentPlaylistDisposable;
TGMusicPlayerPlaylist *_currentPlaylist;
TGMusicPlayerPlaylist *_currentTemporaryPlaylist;
id<SDisposable> _routeChangeDisposable;
TGHolder *_proximityChangeHolder;
TGObserverProxy *_proximityChangedNotification;
bool _proximityState;
bool _changingProximity;
}
@end
@implementation TGMusicPlayer
@synthesize playlistMetadata = _playlistMetadata;
- (instancetype)init
{
self = [super init];
if (self != nil)
{
_playingStatusPipe = [[SPipe alloc] init];
_playlistFinishedPipe = [[SPipe alloc] init];
_queue = [[SQueue alloc] init];
_albumArtMulticastManager = [[SMulticastSignalManager alloc] init];
_currentAudioSession = [[SMetaDisposable alloc] init];
_currentRemoteControls = [[SMetaDisposable alloc] init];
__weak TGMusicPlayer *weakSelf = self;
_routeChangeDisposable = [[[TGAudioSessionManager routeChange] deliverOn:_queue] startWithNext:^(id next)
{
__strong TGMusicPlayer *strongSelf = weakSelf;
if (strongSelf != nil)
{
[strongSelf->_queue dispatch:^
{
if (!strongSelf->_changingProximity) {
if ([next intValue] == TGAudioSessionRouteChangePause)
[strongSelf controlPause];
else
{
if (strongSelf->_currentStatus.item != nil && !strongSelf->_currentStatus.paused)
{
[strongSelf->_player pause];
[strongSelf->_player play];
}
}
}
}];
}
}];
_proximityChangedNotification = [[TGObserverProxy alloc] initWithTarget:self targetSelector:@selector(proximityChanged:) name:TGDeviceProximityStateChangedNotification object:nil];
_proximityChangeHolder = [[TGHolder alloc] init];
}
return self;
}
- (void)dealloc
{
[_currentAudioSession dispose];
[_currentRemoteControls dispose];
[_routeChangeDisposable dispose];
}
- (SSignal *)currentStatusAsync
{
__weak TGMusicPlayer *weakSelf = self;
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
__strong TGMusicPlayer *strongSelf = weakSelf;
if (strongSelf != nil)
{
[strongSelf->_queue dispatch:^
{
[subscriber putNext:strongSelf->_currentStatus];
[subscriber putCompletion];
}];
}
return nil;
}];
}
- (SSignal *)playingStatus
{
return [[[self currentStatusAsync] then:_playingStatusPipe.signalProducer()] deliverOn:[SQueue mainQueue]];
}
- (SSignal *)playlistFinished {
return _playlistFinishedPipe.signalProducer();
}
- (void)updateAudioSession {
[_queue dispatch:^{
if (_currentPlaylist == nil) {
[TGAppDelegateInstance.deviceProximityListeners removeHolder:_proximityChangeHolder];
[_currentAudioSession setDisposable:nil];
} else {
__weak TGMusicPlayer *weakSelf = self;
bool headset = [TGMusicPlayer isHeadsetPluggedIn];
bool overridePort = _currentPlaylist.voice && _proximityState && !headset;
if (_currentPlaylist.voice && !headset) {
[TGAppDelegateInstance.deviceProximityListeners addHolder:_proximityChangeHolder];
} else {
[TGAppDelegateInstance.deviceProximityListeners removeHolder:_proximityChangeHolder];
}
[_currentAudioSession setDisposable:[[TGAudioSessionManager instance] requestSessionWithType:overridePort ? TGAudioSessionTypePlayAndRecordHeadphones : (_currentPlaylist.voice && !headset ? TGAudioSessionTypePlayAndRecord : TGAudioSessionTypePlayMusic) interrupted:^{
__strong TGMusicPlayer *strongSelf = weakSelf;
if (strongSelf != nil && !strongSelf->_changingProximity) {
[strongSelf pauseMedia];
}
}]];
}
}];
}
- (void)requestControlsWithPlay:(bool)play
{
[_queue dispatch:^
{
__weak TGMusicPlayer *weakSelf = self;
[_currentRemoteControls setDisposable:[[TGRemoteControlsManager instance] requestControlsWithPrevious:^
{
__strong TGMusicPlayer *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf controlPrevious];
} next:^
{
__strong TGMusicPlayer *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf controlNext];
} play:play ? ^
{
__strong TGMusicPlayer *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf controlPlay];
} : nil pause: !play ? ^
{
__strong TGMusicPlayer *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf controlPause];
} : nil]];
}];
}
- (void)cancelAudioSessionRequest
{
[_queue dispatch:^
{
[_currentAudioSession setDisposable:nil];
[_currentRemoteControls setDisposable:nil];
}];
}
- (void)pauseMedia
{
[_queue dispatch:^
{
if (_currentStatus != nil && !_currentStatus.paused)
{
[_updateTimer invalidate];
_updateTimer = nil;
CGFloat duration = _player.duration;
CGFloat position = _player.playbackPosition;
CGFloat offset = 0.0f;
if (duration != duration || duration < FLT_EPSILON)
duration = [[TGMusicPlayer attributesForItem:_currentStatus.item][@"duration"] intValue];
if (!isnan(duration) && duration > FLT_EPSILON)
{
if (!isnan(position) && position > FLT_EPSILON)
offset = position / duration;
else
offset = 0.0f;
}
else
duration = 0.0f;
[self setCurrentStatus:[[TGMusicPlayerStatus alloc] initWithItem:_currentStatus.item position:_currentStatus.position paused:true offset:offset duration:duration albumArt:_currentStatus.albumArt albumArtSync:_currentStatus.albumArtSync downloadedStatus:_currentStatus.downloadedStatus isVoice:_currentStatus.isVoice shuffle:_currentStatus.shuffle repeatType:_currentStatus.repeatType]];
}
[_player pause:^{
}];
}];
}
- (CGFloat)updatePositionTimerTimeout
{
return 10.0 / 60.0f;
}
- (void)startUpdatePositionTimer
{
[_updateTimer invalidate];
__weak TGMusicPlayer *weakSelf = self;
_updateTimer = [[STimer alloc] initWithTimeout:[self updatePositionTimerTimeout] repeat:true completion:^
{
__strong TGMusicPlayer *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf updateScrubbingPosition];
} queue:_queue];
[self updateScrubbingPosition];
[_updateTimer start];
}
- (void)playMediaFromItem:(TGMusicPlayerItem *)item
{
[self playMediaFromItem:item force:false];
}
- (void)playMediaFromItem:(TGMusicPlayerItem *)item force:(bool)force
{
[_queue dispatch:^
{
if (_currentStatus != nil && [_currentStatus.item.key isEqual:item.key] && !force)
{
if (_currentStatus.downloadedStatus.downloaded)
{
if (_currentStatus.paused)
{
CGFloat duration = _player.duration;
CGFloat position = _player.absolutePlaybackPosition;
CGFloat offset = 0.0f;
if (duration != duration || duration < FLT_EPSILON)
duration = [[TGMusicPlayer attributesForItem:item][@"duration"] intValue];
if (!isnan(duration) && duration > FLT_EPSILON)
{
if (!isnan(position) && position > FLT_EPSILON)
offset = position / duration;
else
offset = 0.0f;
}
else
duration = 0.0f;
[self setCurrentStatus:[[TGMusicPlayerStatus alloc] initWithItem:_currentStatus.item position:_currentStatus.position paused:false offset:offset duration:duration albumArt:_currentStatus.albumArt albumArtSync:_currentStatus.albumArtSync downloadedStatus:_currentStatus.downloadedStatus isVoice:_currentStatus.isVoice shuffle:_currentStatus.shuffle repeatType:_currentStatus.repeatType]];
[self requestControlsWithPlay:false];
[_player play];
[self startUpdatePositionTimer];
}
else
{
[_updateTimer invalidate];
_updateTimer = nil;
CGFloat duration = _player.duration;
CGFloat position = (CGFloat)_player.absolutePlaybackPosition;
CGFloat offset = 0.0f;
if (duration != duration || duration < FLT_EPSILON)
duration = [[TGMusicPlayer attributesForItem:item][@"duration"] intValue];
if (!isnan(duration) && duration > FLT_EPSILON)
{
if (!isnan(position) && position > FLT_EPSILON)
offset = position / duration;
else
offset = 0.0f;
}
else
duration = 0.0f;
[self setCurrentStatus:[[TGMusicPlayerStatus alloc] initWithItem:_currentStatus.item position:_currentStatus.position paused:true offset:offset duration:duration albumArt:_currentStatus.albumArt albumArtSync:_currentStatus.albumArtSync downloadedStatus:_currentStatus.downloadedStatus isVoice:_currentStatus.isVoice shuffle:_currentStatus.shuffle repeatType:_currentStatus.repeatType]];
if (_player != nil) {
[_player pause:^{
[self requestControlsWithPlay:true];
}];
} else {
[self requestControlsWithPlay:true];
}
}
}
}
else
{
[_updateTimer invalidate];
_updateTimer = nil;
if (item == nil)
{
[_currentItemDisposable setDisposable:nil];
[self setCurrentStatus:nil];
if (_player != nil) {
[_player pause:^{
[self cancelAudioSessionRequest];
}];
} else {
[self cancelAudioSessionRequest];
}
_player = nil;
[self updateNextItemAvailability];
}
else
{
[_player stop];
_player = nil;
__weak TGMusicPlayer *weakSelf = self;
CGFloat duration = [[TGMusicPlayer attributesForItem:item][@"duration"] intValue];
TGMusicPlayerItemPosition itemPosition = [TGMusicPlayer itemPosition:item inArray:_currentPlaylist.items];
[_nextItemDisposable setDisposable:nil];
if (_currentItemDisposable == nil)
_currentItemDisposable = [[SMetaDisposable alloc] init];
[_currentItemDisposable setDisposable:[[[TGMusicPlayerItemSignals itemAvailability:item priority:true] deliverOn:_queue] startWithNext:^(id next)
{
TGMusicPlayerItemAvailability availability = TGMusicPlayerItemAvailabilityUnpack([next longLongValue]);
__strong TGMusicPlayer *strongSelf = weakSelf;
if (strongSelf != nil)
{
if (availability.downloaded)
{
if (strongSelf->_player == nil)
[strongSelf playItem:item];
}
else
{
[strongSelf setCurrentStatus:[[TGMusicPlayerStatus alloc] initWithItem:item position:itemPosition paused:true offset:0.0f duration:duration albumArt:nil albumArtSync:nil downloadedStatus:TGMusicPlayerDownloadingStatusMake(false, availability.downloading, availability.progress) isVoice:item.isVoice shuffle:_currentStatus.shuffle repeatType:_currentStatus.repeatType]];
}
}
}]];
[self updateNextItemAvailability];
}
}
}];
}
- (void)playItem:(TGMusicPlayerItem *)item
{
if (_currentPlaylist.markItemAsViewed) {
_currentPlaylist.markItemAsViewed(item);
}
NSString *path = [TGMusicPlayerItemSignals pathForItem:item];
if ([path pathExtension].length == 0)
{
[[NSFileManager defaultManager] createSymbolicLinkAtPath:[path stringByAppendingString:@".mp3"] withDestinationPath:[path lastPathComponent] error:nil];
path = [path stringByAppendingString:@".mp3"];
}
_player = [[TGModernConversationAudioPlayer alloc] initWithFilePath:path music:!_currentPlaylist.voice controlAudioSession:false];
_player.delegate = self;
CGFloat duration = _player.duration;
CGFloat position = (CGFloat)_player.absolutePlaybackPosition;
CGFloat offset = 0.0f;
if (isnan(duration) || duration < FLT_EPSILON)
duration = [[TGMusicPlayer attributesForItem:item][@"duration"] intValue];
if (!isnan(duration) && duration > FLT_EPSILON)
{
if (!isnan(position) && position > FLT_EPSILON)
offset = position / duration;
else
offset = 0.0f;
}
else
duration = 0.0f;
NSURL *itemUrl = [NSURL fileURLWithPath:path];
TGMusicPlayerItemPosition itemPosition = [TGMusicPlayer itemPosition:item inArray:_currentPlaylist.items];
[self setCurrentStatus:[[TGMusicPlayerStatus alloc] initWithItem:item position:itemPosition paused:false offset:offset duration:duration albumArt:[TGMusicPlayer albumArtForUrl:itemUrl multicastManager:_albumArtMulticastManager] albumArtSync:[TGMusicPlayer albumArtSyncForUrl:itemUrl] downloadedStatus:TGMusicPlayerDownloadingStatusMake(true, false, 1.0f) isVoice:item.isVoice shuffle:(!item.isVoice && _currentStatus.shuffle) repeatType:!item.isVoice ? _currentStatus.repeatType : TGMusicPlayerRepeatTypeNone]];
[self requestControlsWithPlay:false];
[_player play];
[self startUpdatePositionTimer];
}
- (void)updateNextItemAvailability
{
TGMusicPlayerItem *nextItem = nil;
if (_currentStatus.item != nil)
{
NSInteger index = -1;
for (TGMusicPlayerItem *item in _currentPlaylist.items)
{
index++;
if (TGObjectCompare(_currentStatus.item.key, item.key))
{
if (index + 1 < (NSInteger)_currentPlaylist.items.count)
nextItem = _currentPlaylist.items[index + 1];
break;
}
}
}
if (!TGObjectCompare(nextItem.key, _currentNextItem.key))
{
_currentNextItem = nextItem;
if (_currentNextItem != nil)
{
if (_nextItemDisposable == nil)
_nextItemDisposable = [[SMetaDisposable alloc] init];
[_nextItemDisposable setDisposable:nil];
[_nextItemDisposable setDisposable:[[[TGMusicPlayerItemSignals itemAvailability:_currentNextItem priority:false] deliverOn:_queue] startWithNext:^(__unused id next)
{
}]];
}
else
[_nextItemDisposable setDisposable:nil];
}
}
+ (TGMusicPlayerItemPosition)itemPosition:(TGMusicPlayerItem *)item inArray:(NSArray *)array
{
NSInteger index = -1;
for (TGMusicPlayerItem *listItem in array)
{
index++;
if (TGObjectCompare(listItem.key, item.key))
return (TGMusicPlayerItemPosition){.index = (NSUInteger)index, .count = array.count};
}
return (TGMusicPlayerItemPosition){.index = 0, .count = 1};
}
- (id)playlistMetadata {
id result = nil;
@synchronized(self) {
result = _playlistMetadata;
}
return result;
}
- (void)setPlaylist:(SSignal *)playlist initialItemKey:(id<NSCopying>)initialItemKey metadata:(id)metadata {
@synchronized(self) {
_playlistMetadata = metadata;
}
[_queue dispatch:^{
if (_currentPlaylistDisposable == nil)
_currentPlaylistDisposable = [[SMetaDisposable alloc] init];
[_currentPlaylistDisposable setDisposable:nil];
if (playlist == nil)
[self _setPlaylist:nil initialItemKey:nil forceRestart:true];
else
{
__weak TGMusicPlayer *weakSelf = self;
id<SDisposable> delayedSwitchItemDisposable = [[[[SSignal complete] onStart:^
{
__strong TGMusicPlayer *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf playMediaFromItem:nil];
}] delay:1.0 onQueue:_queue] startWithNext:nil];
__block bool firstValue = true;
[_currentPlaylistDisposable setDisposable:[[playlist deliverOn:_queue] startWithNext:^(TGMusicPlayerPlaylist *value)
{
[delayedSwitchItemDisposable dispose];
__strong TGMusicPlayer *strongSelf = weakSelf;
if (strongSelf != nil)
{
[strongSelf _setPlaylist:value initialItemKey:initialItemKey forceRestart:firstValue];
firstValue = false;
}
}]];
}
}];
}
- (void)_setPlaylist:(TGMusicPlayerPlaylist *)playlist initialItemKey:(id<NSCopying>)initialItemKey forceRestart:(bool)forceRestart
{
[_queue dispatch:^
{
if (!TGObjectCompare(_currentPlaylist, playlist))
{
TGMusicPlayerPlaylist *previousPlaylist = _currentPlaylist;
_currentPlaylist = playlist;
if (playlist != nil) {
[self updateAudioSession];
}
if (_currentPlaylist == nil || _currentPlaylist.items.count == 0)
[self playMediaFromItem:nil];
else
{
bool currentItemFound = false;
id<NSObject> nextItemKey = nil;
if (!forceRestart && _currentStatus != nil)
{
bool match = false;
bool found = false;
for (TGMusicPlayerItem *previousItem in previousPlaylist.items)
{
if ([previousItem.key isEqual:_currentStatus.item.key]) {
match = true;
}
if (match) {
for (TGMusicPlayerItem *item in _currentPlaylist.items)
{
if ([item.key isEqual:_currentStatus.item.key])
{
TGMusicPlayerItemPosition itemPosition = [TGMusicPlayer itemPosition:item inArray:_currentPlaylist.items];
[self setCurrentStatus:[[TGMusicPlayerStatus alloc] initWithItem:item position:itemPosition paused:_currentStatus.paused offset:_currentStatus.offset duration:_currentStatus.duration albumArt:_currentStatus.albumArt albumArtSync:_currentStatus.albumArtSync downloadedStatus:_currentStatus.downloadedStatus isVoice:_currentStatus.isVoice shuffle:_currentStatus.shuffle repeatType:_currentStatus.repeatType]];
currentItemFound = true;
found = true;
break;
} else if ([item.key isEqual:previousItem.key]) {
nextItemKey = item.key;
found = true;
break;
}
}
}
if (found) {
break;
}
}
if (!currentItemFound)
{
id<NSObject, NSCopying> aliasKey = _currentPlaylist.itemKeyAliases[_currentStatus.item.key];
if (aliasKey != nil)
{
for (TGMusicPlayerItem *item in _currentPlaylist.items)
{
if ([aliasKey isEqual:_currentStatus.item.key])
{
TGMusicPlayerItemPosition itemPosition = [TGMusicPlayer itemPosition:item inArray:_currentPlaylist.items];
[self setCurrentStatus:[[TGMusicPlayerStatus alloc] initWithItem:item position:itemPosition paused:_currentStatus.paused offset:_currentStatus.offset duration:_currentStatus.duration albumArt:_currentStatus.albumArt albumArtSync:_currentStatus.albumArtSync downloadedStatus:_currentStatus.downloadedStatus isVoice:_currentStatus.isVoice shuffle:_currentStatus.shuffle repeatType:_currentStatus.repeatType]];
currentItemFound = true;
break;
}
}
}
}
}
if (!currentItemFound) {
if (nextItemKey != nil) {
for (TGMusicPlayerItem *item in _currentPlaylist.items)
{
if ([item.key isEqual:nextItemKey])
{
[self playMediaFromItem:item];
currentItemFound = true;
break;
}
}
} else if (initialItemKey != nil) {
for (TGMusicPlayerItem *item in _currentPlaylist.items)
{
if ([item.key isEqual:initialItemKey])
{
[self playMediaFromItem:item];
currentItemFound = true;
break;
}
}
}
}
if (!currentItemFound)
[self playMediaFromItem:_currentPlaylist.items.firstObject];
}
[self updateNextItemAvailability];
if (playlist == nil) {
[self updateAudioSession];
}
}
}];
}
- (void)controlPlay
{
[_queue dispatch:^
{
if (_currentStatus != nil && _currentStatus.paused)
[self playMediaFromItem:_currentStatus.item];
}];
}
- (void)controlPause {
[self controlPause:nil];
}
- (void)controlPause:(void (^)())completion
{
[_queue dispatch:^
{
if (_currentStatus != nil && !_currentStatus.paused)
[self playMediaFromItem:_currentStatus.item];
if (completion) {
completion();
}
}];
}
- (void)controlAdvance:(bool)forward
{
[_queue dispatch:^
{
if (_currentPlaylist.items.count != 0)
{
if (_currentStatus.item != nil)
{
NSInteger index = -1;
for (TGMusicPlayerItem *item in _currentPlaylist.items)
{
index++;
if (TGObjectCompare(item.key, _currentStatus.item.key))
{
NSInteger nextIndex = 0;
if (forward)
{
nextIndex = index + 1;
if (nextIndex >= (NSInteger)_currentPlaylist.items.count)
nextIndex = 0;
}
else
{
if (_currentStatus.duration == _currentStatus.duration && _currentStatus.duration > FLT_EPSILON && _currentStatus.offset * _currentStatus.duration > 5.0)
{
nextIndex = index;
}
else
{
nextIndex = index - 1;
if (nextIndex < 0)
nextIndex = _currentPlaylist.items.count - 1;
}
}
if (nextIndex == index)
{
[self _seekToPosition:0.0];
[self controlPlay];
}
else
[self playMediaFromItem:_currentPlaylist.items[nextIndex]];
break;
}
}
}
else
[self playMediaFromItem:_currentPlaylist.items.firstObject];
}
}];
}
- (void)controlNext
{
[self controlAdvance:true];
}
- (void)controlPrevious
{
[self controlAdvance:false];
}
- (void)controlSeekToPosition:(CGFloat)position
{
[_queue dispatch:^
{
CGFloat duration = _player.duration;
if (!isnan(duration) && duration > FLT_EPSILON)
[self _seekToPosition:duration * position];
}];
}
- (void)_dispatch:(dispatch_block_t)block {
[_queue dispatch:^{
if (block) {
block();
}
}];
}
- (void)controlShuffle {
[_queue dispatch:^{
[self setCurrentStatus:[[TGMusicPlayerStatus alloc] initWithItem:_currentStatus.item position:_currentStatus.position paused:_currentStatus.paused offset:_currentStatus.offset duration:_currentStatus.duration albumArt:_currentStatus.albumArt albumArtSync:_currentStatus.albumArtSync downloadedStatus:_currentStatus.downloadedStatus isVoice:_currentStatus.isVoice shuffle:!_currentStatus.shuffle repeatType:_currentStatus.repeatType]];
}];
}
- (void)controlRepeat {
[_queue dispatch:^ {
TGMusicPlayerRepeatType repeatType = _currentStatus.repeatType;
switch (repeatType) {
case TGMusicPlayerRepeatTypeNone:
repeatType = TGMusicPlayerRepeatTypeAll;
break;
case TGMusicPlayerRepeatTypeAll:
repeatType = TGMusicPlayerRepeatTypeOne;
break;
default:
repeatType = TGMusicPlayerRepeatTypeNone;
break;
}
[self setCurrentStatus:[[TGMusicPlayerStatus alloc] initWithItem:_currentStatus.item position:_currentStatus.position paused:_currentStatus.paused offset:_currentStatus.offset duration:_currentStatus.duration albumArt:_currentStatus.albumArt albumArtSync:_currentStatus.albumArtSync downloadedStatus:_currentStatus.downloadedStatus isVoice:_currentStatus.isVoice shuffle:_currentStatus.shuffle repeatType:repeatType]];
}];
}
- (void)setCurrentStatus:(TGMusicPlayerStatus *)currentStatus
{
TGMusicPlayerStatus *previousStatus = _currentStatus;
_currentStatus = currentStatus;
_playingStatusPipe.sink(currentStatus);
if (!TGObjectCompare(currentStatus.item.key, previousStatus.item.key))
{
if (currentStatus.item == nil)
{
[_currentAlbumArtDisposable dispose];
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:nil];
}
else
{
NSDictionary *attributes = [TGMusicPlayer attributesForItem:currentStatus.item];
if (_currentAlbumArtDisposable == nil)
_currentAlbumArtDisposable = [[SMetaDisposable alloc] init];
NSString *path = [TGMusicPlayerItemSignals pathForItem:currentStatus.item];
[_currentAlbumArtDisposable setDisposable:[[[[SSignal single:nil] then:[TGMusicPlayer albumArtForUrl:[NSURL fileURLWithPath:path] multicastManager:_albumArtMulticastManager]] deliverOn:[SQueue mainQueue]] startWithNext:^(UIImage *image)
{
NSMutableDictionary *songInfo = [[NSMutableDictionary alloc] init];
if (image != nil)
{
MPMediaItemArtwork *albumArt = [[MPMediaItemArtwork alloc] initWithImage:image];
[songInfo setObject:albumArt forKey:MPMediaItemPropertyArtwork];
}
NSString *title = @"";
NSString *performer = @"";
if (attributes[@"title"] != nil)
title = attributes[@"title"];
if (attributes[@"performer"] != nil)
performer = attributes[@"performer"];
if (title.length == 0)
{
if ([currentStatus.item.media isKindOfClass:[TGDocumentMediaAttachment class]]) {
title = ((TGDocumentMediaAttachment *)currentStatus.item.media).fileName;
for (id attribute in ((TGDocumentMediaAttachment *)currentStatus.item.media).attributes) {
if ([attribute isKindOfClass:[TGDocumentAttributeAudio class]]) {
if (((TGDocumentAttributeAudio *)attribute).isVoice) {
title = TGLocalized(@"MusicPlayer.VoiceNote");
performer = @"Telegram";
}
break;
}
}
}
}
if (title.length == 0)
title = @"Unknown Track";
if (performer.length == 0)
performer = @"Unknown Artist";
[songInfo setObject:title forKey:MPMediaItemPropertyTitle];
[songInfo setObject:performer forKey:MPMediaItemPropertyArtist];
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:songInfo];
}]];
}
}
}
- (void)_seekToPosition:(NSTimeInterval)position
{
[_queue dispatch:^
{
NSTimeInterval duration = _player.duration;
if (duration > DBL_EPSILON) {
[_player play:(float)(position / duration)];
}
}];
}
- (void)updateScrubbingPosition
{
if (_player != nil)
{
CGFloat duration = _player.duration;
CGFloat position = _player.absolutePlaybackPosition;
CGFloat offset = 0.0f;
if (duration != duration || duration < FLT_EPSILON)
duration = [[TGMusicPlayer attributesForItem:_currentStatus.item][@"duration"] intValue];
if (!isnan(duration) && duration > FLT_EPSILON)
{
if (!isnan(position) && position > FLT_EPSILON)
offset = position / duration;
else
offset = 0.0f;
}
[self setCurrentStatus:[[TGMusicPlayerStatus alloc] initWithItem:_currentStatus.item position:_currentStatus.position paused:false offset:offset duration:duration albumArt:_currentStatus.albumArt albumArtSync:_currentStatus.albumArtSync downloadedStatus:_currentStatus.downloadedStatus isVoice:_currentStatus.isVoice shuffle:_currentStatus.shuffle repeatType:_currentStatus.repeatType]];
}
}
- (void)audioPlayerDidFinish
{
[_queue dispatch:^
{
if (_currentStatus.item != nil)
{
if (_currentStatus.repeatType == TGMusicPlayerRepeatTypeOne)
{
[self playMediaFromItem:_currentStatus.item force:true];
}
else
{
NSInteger index = -1;
for (TGMusicPlayerItem *item in _currentPlaylist.items)
{
index++;
if (TGObjectCompare(item.key, _currentStatus.item.key))
{
if (index == (NSInteger)_currentPlaylist.items.count - 1)
{
if (_currentPlaylist.voice) {
id metadata = [self playlistMetadata];
[self setPlaylist:nil initialItemKey:nil metadata:nil];
[self requestControlsWithPlay:true];
_playlistFinishedPipe.sink(metadata);
} else {
if (_currentStatus.repeatType == TGMusicPlayerRepeatTypeNone)
{
[self _seekToPosition:0.0f];
if (_player != nil) {
[_player pause:^{
[self requestControlsWithPlay:true];
}];
} else {
[self requestControlsWithPlay:true];
}
[_updateTimer invalidate];
_updateTimer = nil;
CGFloat duration = _player.duration;
if (isnan(duration) || duration < FLT_EPSILON)
duration = 0.0f;
[self setCurrentStatus:[[TGMusicPlayerStatus alloc] initWithItem:_currentStatus.item position:_currentStatus.position paused:true offset:0.0f duration:duration albumArt:_currentStatus.albumArt albumArtSync:_currentStatus.albumArtSync downloadedStatus:_currentStatus.downloadedStatus isVoice:_currentStatus.isVoice shuffle:_currentStatus.shuffle repeatType:_currentStatus.repeatType]];
}
else
{
[self playMediaFromItem:_currentPlaylist.items.firstObject];
}
}
}
else
[self controlAdvance:true];
break;
}
}
}
}
}];
}
+ (SSignal *)albumArtSyncForUrl:(NSURL *)url
{
return [[[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
AVAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
if (asset == nil)
[subscriber putError:nil];
NSArray *artworks = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyArtwork keySpace:AVMetadataKeySpaceCommon];
if (artworks == nil)
[subscriber putError:nil];
else
{
UIImage *image = nil;
for (AVMetadataItem *item in artworks)
{
if ([item.keySpace isEqualToString:AVMetadataKeySpaceID3])
{
if ([item.value respondsToSelector:@selector(objectForKey:)])
image = [UIImage imageWithData:[(id)item.value objectForKey:@"data"]];
else if ([item.value isKindOfClass:[NSData class]])
image = [UIImage imageWithData:(id)item.value];
}
else if ([item.keySpace isEqualToString:AVMetadataKeySpaceiTunes])
image = [UIImage imageWithData:(id)item.value];
}
if (image != nil)
{
CGSize screenSize = TGScreenSize();
CGFloat screenSide = MIN(screenSize.width, screenSize.height);
CGFloat scale = TGIsRetina() ? 1.7f : 1.0f;
CGSize pixelSize = CGSizeMake(screenSide * scale, screenSide * scale);
image = TGScaleImageToPixelSize(image, TGFitSize(CGSizeMake(image.size.width * image.scale, image.size.height * image.scale), pixelSize));
[subscriber putNext:image];
[subscriber putCompletion];
}
else
[subscriber putError:nil];
}
return nil;
}] catch:^SSignal *(__unused id error)
{
return [self albumArtForUrl:url multicastManager:nil];
}];
}
+ (SSignal *)albumArtForUrl:(NSURL *)url multicastManager:(SMulticastSignalManager *)__unused multicastManager
{
/*return [multicastManager multicastedSignalForKey:url.absoluteString producer:^SSignal *
{*/
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
__block bool cancelled = false;
AVAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
if (asset == nil)
[subscriber putError:nil];
else
{
[asset loadValuesAsynchronouslyForKeys:@[@"commonMetadata"] completionHandler:^
{
if (cancelled)
return;
NSArray *artworks = [AVMetadataItem metadataItemsFromArray:asset.commonMetadata withKey:AVMetadataCommonKeyArtwork keySpace:AVMetadataKeySpaceCommon];
UIImage *image = nil;
for (AVMetadataItem *item in artworks)
{
if ([item.keySpace isEqualToString:AVMetadataKeySpaceID3])
{
if ([item.value respondsToSelector:@selector(objectForKey:)])
image = [UIImage imageWithData:[(id)item.value objectForKey:@"data"]];
else if ([item.value isKindOfClass:[NSData class]])
image = [UIImage imageWithData:(id)item.value];
}
else if ([item.keySpace isEqualToString:AVMetadataKeySpaceiTunes])
image = [UIImage imageWithData:(id)item.value];
}
if (image != nil)
{
CGSize screenSize = TGScreenSize();
CGFloat screenSide = MIN(screenSize.width, screenSize.height);
CGFloat scale = TGIsRetina() ? 1.7f : 1.0f;
CGSize pixelSize = CGSizeMake(screenSide * scale, screenSide * scale);
image = TGScaleImageToPixelSize(image, TGFitSize(CGSizeMake(image.size.width * image.scale, image.size.height * image.scale), pixelSize));
[subscriber putNext:image];
[subscriber putCompletion];
}
else
{
[subscriber putError:nil];
}
}];
}
return [[SBlockDisposable alloc] initWithBlock:^
{
cancelled = true;
}];
}];
//}];
}
+ (NSDictionary *)attributesForItem:(TGMusicPlayerItem *)item
{
if ([item.media isKindOfClass:[TGDocumentMediaAttachment class]]) {
for (id attribute in ((TGDocumentMediaAttachment *)item.media).attributes)
{
if ([attribute isKindOfClass:[TGDocumentAttributeAudio class]])
{
TGDocumentAttributeAudio *audioAttribute = attribute;
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
if (audioAttribute.title != nil)
dict[@"title"] = audioAttribute.title;
if (audioAttribute.performer != nil)
dict[@"performer"] = audioAttribute.performer;
dict[@"duration"] = @(audioAttribute.duration);
return dict;
}
}
} else if ([item.media isKindOfClass:[TGAudioMediaAttachment class]]) {
return @{@"duration": @(((TGAudioMediaAttachment *)item.media).duration)};
}
return @{};
}
+ (bool)isHeadsetPluggedIn
{
AVAudioSessionRouteDescription* route = [[AVAudioSession sharedInstance] currentRoute];
for (AVAudioSessionPortDescription *desc in [route outputs])
{
if ([[desc portType] isEqualToString:AVAudioSessionPortHeadphones])
return true;
}
return false;
}
- (void)proximityChanged:(NSNotification *)__unused notification
{
bool proximityState = TGAppDelegateInstance.deviceProximityState;
[_queue dispatch:^{
_proximityState = proximityState;
if (_currentPlaylist.voice && _currentStatus != nil && ![TGMusicPlayer isHeadsetPluggedIn]) {
_changingProximity = true;
[self updateAudioSession];
_changingProximity = false;
if (_proximityState) {
if (_currentStatus.paused) {
[self controlPlay];
}
} else {
[self controlPause];
}
}
}];
}
@end