ㄷㅣㅆㅣ's Amusement

[iOS/Objective-C] Audio Player tutorial using AVPlayer (AVFoundation) -- 1 본문

Programming/iOS

[iOS/Objective-C] Audio Player tutorial using AVPlayer (AVFoundation) -- 1

ㄷㅣㅆㅣ 2016. 2. 24. 12:10

[iOS/Objective-C] Audio Player tutorial using AVPlayer (AVFoundation) -- 1

반응형

  이전에 비디오 플레이어 튜토리얼을 포스팅 했으니(2016/02/23 - [프로그래밍/iOS] - [iOS/Objective-C] Video Player tutorial using AVPlayer (AVFoundation)), 여세를 몰아 오디오 플레이어도 만들어보자.

  Last posting is Video Player Tutorial(2016/02/23 - [프로그래밍/iOS] - [iOS/Objective-C] Video Player tutorial using AVPlayer (AVFoundation)). so We'll make a simple audio player this time.


- 요구사항

  1) 백그라운드에서 플레이 되어야 한다.

  2) iOS의 Now Playing Info Center에 현재상태가 보여야 하고, 조정도 가능해야한다.

  3) 알림음과 다른 볼륨으로 가져가야한다.

  4) 벨소리/전화와 같은 인터럽트가 발생한 이후 다시 재생되어야 한다.

- Requirement

  1) It can play music background

  2) It can display current status on Now Playing Info Center(You can check if you swipe bottom to middle on your iPhone/iPad screen) and control music on it.

  3) The music volume must be different from Alarm sound

  4) It can be resumed after bell-ringing or phone call interrupt.


[1편에서는 일단 play하는 것 까지만 다룬다]

[At this time, I will post Just How to play music using AVPlayer/AVFoundation]


1. AudioManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@import AVFoundation;
@import MediaPlayer;
@import AudioToolbox;
#import <Foundation/Foundation.h>
#import "CFile.h"
 
@protocol AudioManagerDelegate <NSObject>
@optional
- (void)metadataExtracted;
- (void)playerStateChanged:(BOOL)isPlaying;
@end
 
@interface AudioManager : NSObject <AVAudioPlayerDelegate>
@property (nonatomic) NSInteger curTrackIndex;
@property (strong, nonatomic) NSArray<CFile *> *playlist;
@property (strong, nonatomic) NSString *curTitle;
@property (strong, nonatomic) NSString *curAlbumName;
@property (strong, nonatomic) NSString *curArtist;
@property (strong, nonatomic) UIImage *curAlbumImg;
 
@property (nonatomic, assign) id<AudioManagerDelegate> delegate;
 
+ (instancetype)sharedInstance;
- (void)prepareToPlay:(void (^)(BOOL bSuccess))complete;
- (void)playAudio;
- (void)pauseAudio;

// For auto playing prev/next music. (looping playlist)
- (void)playPrevAudioAutoStart:(BOOL)bAutoStart;
- (void)playNextAudioAutoStart:(BOOL)bAutoStart;

// Common control function
- (void)seekTo:(NSTimeInterval)value;
- (float)curAudioDuration;
- (NSTimeInterval)curAudioTime;
- (NSString *)curTrackName;

// Event from Now Playing Center
- (void)handleControlReceivedWithEvent:(UIEvent *)event;
@end
cs
 - 뷰 컨트롤러가 사라진 후에도 음악을 재생하려면 오디오 매니저를 두어서 처리한다.

 - If you want to play music when the view-controller dismissed, you can make AudioManager and use it in view-controller.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
- (void)prepareToPlay:(void (^)(BOOL bSuccess))complete {
 
    __block BOOL bPrepared = NO;
    
    if (filePath == nil) {
        // download audio files
        [self downloadAudioFile:cFile complete:^(BOOL bSuccess) {
            if (bSuccess) {
                NSURL *audioFileUrl = [NSURL fileURLWithPath:[cFile.filePath fullPath]];
                bPrepared = [self prepareAudioPlayerWithUrl:audioFileUrl];
            }
            
            if (complete) {
                complete (bSuccess && bPrepared);
            }
        }];
    } else {
        NSURL *audioFileUrl = [NSURL fileURLWithPath:filePath fullPath];
        bPrepared = [self prepareAudioPlayerWithUrl:audioFileUrl];
        
        if (complete) {
            complete(bPrepared);
        }
    } 
}
 
 
- (BOOL)prepareAudioPlayerWithUrl:(NSURL *)audioFileUrl {
    BOOL bPrepared = NO;
    NSError *error;
    self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioFileUrl error:&error];
    if (error == nil) {
        self.audioPlayer.delegate = self;
        bPrepared = [self.audioPlayer prepareToPlay];
        // Get metadata cuncurrently
        [self metadataFromFileUrl:audioFileUrl];
    } else {
        NSLog(@"====================== error : %@", error);
    }
    
    return bPrepared;
}
 
 
 
 
- (void)metadataFromFileUrl:(NSURL *)audioFileUrl {
    AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:audioFileUrl options:nil];
    NSArray *keys = @[@"commonMetadata"];
    
    self.curTitle = @"";
    self.curAlbumName = @"";
    self.curArtist = @"";
    self.curAlbumImg = nil;
    
    [asset loadValuesAsynchronouslyForKeys:keys completionHandler:^() {
        NSError *error = nil;
        AVKeyValueStatus metadataStatus = [asset statusOfValueForKey:@"commonMetadata" error:&error];
        switch (metadataStatus) {
            case AVKeyValueStatusLoaded:{
                for (AVMetadataItem *item in [asset commonMetadata]) {
                    if ([item.commonKey isEqualToString:AVMetadataCommonKeyTitle]) {
                        self.curTitle = item.stringValue;
                    } else if ([item.commonKey isEqualToString:AVMetadataCommonKeyAlbumName]) {
                        self.curAlbumName = item.stringValue;
                    } else if ([item.commonKey isEqualToString:AVMetadataCommonKeyArtist]) {
                        self.curArtist = item.stringValue;
                    } else if ([item.commonKey isEqualToString:AVMetadataCommonKeyArtwork]) {
                        self.curAlbumImg = [UIImage imageWithData:[item.value copyWithZone:nil]];
                    }
                }
                
                if ([self.delegate respondsToSelector:@selector(metadataExtracted)]) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        // Metadata notification for view update. 
                        [self.delegate metadataExtracted];
                    });
                }
                
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self updatePlayingInfoCenter];
                });
                break;
            }
                
            default:
                NSLog(@"error - status:%ld, error:%@", (long)metadataStatus, error);
                break;
        }
    }];
}
 
 
- (void)updatePlayingInfoCenter {
    NSMutableDictionary *playingInfo = [NSMutableDictionary dictionary];
    playingInfo[MPMediaItemPropertyAlbumTitle] = self.curAlbumName;
    playingInfo[MPMediaItemPropertyArtist] = self.curArtist;
    playingInfo[MPMediaItemPropertyTitle] = (0<self.curTitle.length?self.curTitle:self.curTrackName);
    playingInfo[MPMediaItemPropertyPlaybackDuration] = @(ceil(self.curAudioDuration));
    playingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = @(self.curAudioTime);
    playingInfo[MPNowPlayingInfoPropertyPlaybackRate] = @(1);
    playingInfo[MPNowPlayingInfoPropertyPlaybackQueueIndex] = @(self.curTrackIndex);
    
    if (self.curAlbumImg != nil) {
        // You can see the album art on lock screen if you set this.
        MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:self.curAlbumImg];
        playingInfo[MPMediaItemPropertyArtwork] = artwork;
    }
    
    [MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = playingInfo;
}
 
 
 
- (void)playAudio {
    [[AVAudioSession sharedInstance] setActive:YES error:nil];
    if ([self.audioPlayer play]) {
        MPNowPlayingInfoCenter *center = [MPNowPlayingInfoCenter defaultCenter];
        // Set current time to Now Playing Center.
 
        // mutable로 안하면 기존 정보가 지워짐. 문서에 안나옴. 왜그런지 모름.
        // You should use mutable dictionary. otherwise, all information you set before will be lost.
 
        NSMutableDictionary *playingInfo = [NSMutableDictionary dictionaryWithDictionary:center.nowPlayingInfo];
        playingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = @(self.audioPlayer.currentTime);
        center.nowPlayingInfo = playingInfo;
    }
    
    if ([self.delegate respondsToSelector:@selector(playerStateChanged:)]) {
        [self.delegate playerStateChanged:self.audioPlayer.isPlaying];
    }
}
 
cs



2. View Controller.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)viewDidLoad {
    [super viewDidLoad];
 
    // running time slide bar setting.
    [self.runningTimeSlider setThumbImage:[UIImage imageNamed:@"YOUR_HANDLE_ICON"] forState:UIControlStateNormal];
    [self.runningTimeSlider setThumbImage:[UIImage imageNamed:@"YOUR_HANDLE_ICON_HIGHLIGHTED"] forState:UIControlStateHighlighted];
    [self.runningTimeSlider setMaximumValue:[[AudioManager sharedInstance] curAudioDuration]];
 
    [self.runningTimeSlider setValue:0.0];
    
    // play music automatically
    [[AudioManager sharedInstance] setDelegate:self];
    [[AudioManager sharedInstance] setPlaylist:self.playlist];
    [[AudioManager sharedInstance] prepareToPlay:^(BOOL bSuccess) {
        [[AudioManager sharedInstance] playAudio];
    }];
}
 
cs

 - Just Use Audio Manager :)

반응형
Comments