// // MediaPlayer.m // XNI // // Created by Matej Jan on 18.1.11. // Copyright 2011 Retronator. All rights reserved. // #import "MediaPlayer.h" #import "MediaPlayer+Internal.h" #import #import "Retronator.Xni.Framework.Media.h" #import "MediaQueue+Internal.h" #import "Song+Internal.h" @interface MediaPlayer () - (BOOL) checkAvailability; - (void) setMediaState:(MediaState)value; - (void) fillSongIndices; - (void) setSongToPlayOnActive:(Song*)song; - (void) toBackground; - (void) returnFromBackground; @end @implementation MediaPlayer static MediaPlayer *instance; + (void) initialize { if (!instance) { instance = [[MediaPlayer alloc] init]; } } - (id) init { self = [super init]; if (self != nil) { // Start in ambient mode so we let user music play until a call to MediaPlayer play changes this. [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryAmbient error: nil]; queue = [[MediaQueue alloc] init]; isMuted = NO; volume = 1; remainingSongIndices = [[NSMutableArray alloc] init]; [queue.activeSongChanged subscribeDelegate:[Delegate delegateWithTarget:self Method:@selector(queueActiveSongChanged)]]; activeSongChanged = [[Event alloc] init]; mediaStateChanged = [[Event alloc] init]; } return self; } - (BOOL) gameHasControl { UInt32 otherAudioIsPlaying; UInt32 propertySize = sizeof(otherAudioIsPlaying); AudioSessionGetProperty ( kAudioSessionProperty_OtherAudioIsPlaying, &propertySize, &otherAudioIsPlaying ); return !otherAudioIsPlaying; } @synthesize isMuted; - (void) setIsMuted:(BOOL)value { isMuted = value; if (isMuted) { queue.activeSong.audioPlayer.volume = 0; } else { queue.activeSong.audioPlayer.volume = volume; } } @synthesize isRepeating; @synthesize isShuffled; - (NSTimeInterval) playPosition { return queue.activeSong.audioPlayer.currentTime; } @synthesize volume; - (void) setVolume:(float)value { volume = MAX(0, MIN(1, value)); if (!isMuted) { queue.activeSong.audioPlayer.volume = volume; } } @synthesize queue, state, mediaStateChanged; - (Event *) activeSongChanged { return queue.activeSongChanged; } + (MediaPlayer*) getInstance { return instance; } + (void) moveNext { [instance moveNext];} + (void) movePrevious { [instance movePrevious];} + (void) pause { [instance pause];} + (void) playSong:(Song*)song { [instance playSong:song];} + (void) resume { [instance resume];} + (void) stop { [instance stop];} + (void) toBackground { [instance toBackground];} + (void) returnFromBackground { [instance returnFromBackground];} - (void) moveNext { if (![self checkAvailability]) { return; } if ([remainingSongIndices count] == 0) { [self fillSongIndices]; } int nextIndex = 0; if (isShuffled) { nextIndex = random() % remainingSongIndices.count; } queue.activeSongIndex = [((NSNumber*)[remainingSongIndices objectAtIndex:nextIndex]) intValue]; [remainingSongIndices removeObjectAtIndex:nextIndex]; } - (void) movePrevious { if (![self checkAvailability]) { return; } if (isShuffled) { queue.activeSongIndex = random() % queue.count; } else { queue.activeSongIndex = (queue.activeSongIndex - 1 + queue.count) % queue.count; } } - (void) pause { if (![self checkAvailability]) { return; } [queue.activeSong.audioPlayer pause]; [self setMediaState:MediaStatePaused]; } - (void) playSong:(Song*)song { if (![self checkAvailability]) { // Save the song if we might get availability later. [self setSongToPlayOnActive:song]; return; } if (queue.activeSong) { [queue.activeSong.audioPlayer stop]; } song.audioPlayer.currentTime = 0; song.audioPlayer.delegate = self; [queue setSong:song]; [self fillSongIndices]; [self moveNext]; [self resume]; } - (void) resume { if (![self checkAvailability] || !queue.activeSong) { return; } [queue.activeSong.audioPlayer play]; [self setMediaState:MediaStatePlaying]; } - (void) stop { if (![self checkAvailability] || !queue.activeSong) { return; } queue.activeSong.audioPlayer.currentTime = 0; [queue.activeSong.audioPlayer stop]; [self setMediaState:MediaStateStopped]; // The music stops, activate the ambient category again. [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryAmbient error:nil]; soloModeActivated = NO; } - (BOOL) checkAvailability { if (!self.gameHasControl) { return NO; } if (!soloModeActivated) { // Switch to solo mode so we silence user audio before playing our music. [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategorySoloAmbient error:nil]; soloModeActivated = YES; } return YES; } - (void) queueActiveSongChanged { [activeSongChanged raiseWithSender:self]; } - (void) setMediaState:(MediaState)value { if (state == value) { return; } state = value; [mediaStateChanged raiseWithSender:self]; } - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag { if ([remainingSongIndices count] == 0 && !isRepeating) { // The music stops, activate the ambient category again. [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryAmbient error:nil]; soloModeActivated = NO; [self setMediaState:MediaStateStopped]; return; } [self moveNext]; [self resume]; } - (void) fillSongIndices { [remainingSongIndices removeAllObjects]; for (int i = 0; i < queue.count; i++) { [remainingSongIndices addObject:[NSNumber numberWithInt:i]]; } } - (void)setSongToPlayOnActive:(Song *)song { [songToPlayOnActive release]; songToPlayOnActive = [song retain]; } - (void)toBackground { // If music was playing, activate the ambient category while the app is in background. if (soloModeActivated) { [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryAmbient error:nil]; } } - (void)returnFromBackground { // If music was playing, try to return to playing if we still have control. // Otherwise the user has started playing his own music and we should remain in ambient. if (soloModeActivated) { if (self.gameHasControl) { // Everything is OK, set category back to solo. [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategorySoloAmbient error:nil]; } else { // Stop music if we lost control. [self setSongToPlayOnActive:queue.activeSong]; queue.activeSong.audioPlayer.currentTime = 0; [queue.activeSong.audioPlayer stop]; [self setMediaState:MediaStateStopped]; soloModeActivated = NO; } } else { if (self.gameHasControl && songToPlayOnActive) { [self playSong:songToPlayOnActive]; [self setSongToPlayOnActive:nil]; } } } - (void) dealloc { [remainingSongIndices release]; [activeSongChanged release]; [mediaStateChanged release]; [songToPlayOnActive release]; [queue release]; [super dealloc]; } @end