English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
It's been a long time since I last wrote something. Lately, I've been working overtime too severely, so today I took some time to organize the music player DOUAudioStreamer I used. Since the project previously used AVPlayer, this one is also okay, but it needs to cache for a while before playback. After the boss saw it, he demanded that the caching and playback should be changed (play immediately when there is an internet connection, click the play button and it will play immediately), why didn't he say it earlier! Why didn't he say it earlier! Why didn't he say it earlier! What else can I do? I can only forgive him and keep coding......(Let's just show the code directly)
First, import third-party library
pod 'DOUAudioStreamer'
Or download from GitHup address:https://github.com/douban/DOUAudioStreamer
Second, use
1Import NAKPlaybackIndicatorView file and MusicIndicator.h and MusicIndicator.m files from the demo and import the header files
//Music playback
#import "DOUAudioStreamer.h"
#import "NAKPlaybackIndicatorView.h"
#import "MusicIndicator.h"
#import "Track.h"
As shown in the figure:
2Create a Track class to store the URL for music playback
3In the interface.h file that requires it, add DOUAudioStreamer and initialize it using singleton
+ (instancetype)sharedInstance ; @property (nonatomic, strong) DOUAudioStreamer *streamer;
As shown in the figure:
Implement in .m file:
static void *kStatusKVOKey = &kStatusKVOKey; static void *kDurationKVOKey = &kDurationKVOKey; static void *kBufferingRatioKVOKey = &kBufferingRatioKVOKey; @property (strong, nonatomic) MusicIndicator *musicIndicator; @property (nonatomic, strong) Track *audioTrack; + (instancetype)sharedInstance { static HYNEntertainmentController *_sharedMusicVC = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _sharedMusicVC = [[HYNEntertainmentController alloc] init]; _sharedMusicVC.streamer = [[DOUAudioStreamer alloc] init]; }); return _sharedMusicVC; }
Play button event
#pragma mark ---Music play button -(void)playMusicStart:(UIButton *)sender { //Get cell through button MusicCollectionViewCell *musicCell = (MusicCollectionViewCell *)[[sender superview] superview]; if(_playFirst == 0) {//_playFirst == 0 for the first play, others are paused NSURL *_audioTrack.audioFileURL = url; @try { [self removeStreamerObserver]; @catch(id anException) { } } //It must be set to nil before playing in DOUAudioStreamer _streamer = nil; _streamer = [DOUAudioStreamer streamerWithAudioFile:_audioTrack]; [self addStreamerObserver]; [_streamer play]; } if([_streamer status] == DOUAudioStreamerPaused || [_streamer status] == DOUAudioStreamerIdle [sender setBackgroundImage:[UIImage imageNamed:@"music_play_icon"] forState:UIControlStateNormal]; [_streamer play]; } else { [sender setBackgroundImage:[UIImage imageNamed:@"music_stop_icon"] forState:UIControlStateNormal]; [_streamer pause]; } _playFirst++; }
Observe the addition of the listener
- (void)addStreamerObserver { [_streamer addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:kStatusKVOKey]; [_streamer addObserver:self forKeyPath:@"duration" options:NSKeyValueObservingOptionNew context:kDurationKVOKey]; [_streamer addObserver:self forKeyPath:@"bufferingRatio" options:NSKeyValueObservingOptionNew context:kBufferingRatioKVOKey]; } /// Player destroyed - (void)dealloc{ if (_streamer !=nil) { [_streamer pause]; [_streamer removeObserver:self forKeyPath:@"status" context:kStatusKVOKey]; [_streamer removeObserver:self forKeyPath:@"duration" context:kDurationKVOKey]; [_streamer removeObserver:self forKeyPath:@"bufferingRatio" context:kBufferingRatioKVOKey]; _streamer =nil; } } - (void)removeStreamerObserver { [_streamer removeObserver:self forKeyPath:@"status"]; [_streamer removeObserver:self forKeyPath:@"duration"]; [_streamer removeObserver:self forKeyPath:@"bufferingRatio"]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == kStatusKVOKey) { [self performSelector:@selector(updateStatus)] onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO]; } else if (context == kDurationKVOKey) { [self performSelector:@selector(updateSliderValue:) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO]; } else if (context == kBufferingRatioKVOKey) { [self performSelector:@selector(updateBufferingStatus) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO]; } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } - (void)updateSliderValue:(id)timer { } -(void)updateBufferingStatus { } - (void)updateStatus { //self.musicIsPlaying = NO; _musicIndicator.state = NAKPlaybackIndicatorViewStateStopped; switch ([_streamer status]) { case DOUAudioStreamerPlaying: // self.musicIsPlaying = YES; _musicIndicator.state = NAKPlaybackIndicatorViewStatePlaying; break; case DOUAudioStreamerPaused: break; case DOUAudioStreamerIdle: break; case DOUAudioStreamerFinished: break; case DOUAudioStreamerBuffering: _musicIndicator.state = NAKPlaybackIndicatorViewStatePlaying; break; case DOUAudioStreamerError: break; } }
This will allow playback.
Music display when the screen is locked, pause playback after pulling out the earphones, and listen to audio interruption events
-(void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; //Accept remote control [self becomeFirstResponder]; [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; } //This cannot be forgotten -(BOOL)canBecomeFirstResponder{ return YES; } - (void)viewDidLoad { [super viewDidLoad]; //Music player [self initPlayer]; } #pragma mark =========================Music Playback============================== //Music player -(void)initPlayer { _audioTrack = [[Track alloc] init]; AVAudioSession *session = [AVAudioSession sharedInstance]; [session setActive:YES error:nil]; [session setCategory:AVAudioSessionCategoryPlayback error:nil]; //Enable the app to accept remote control events [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; //Add notifications to pause playback after pulling out the headphones [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChange:) name:AVAudioSessionRouteChangeNotification object:nil]; // Listen to audio interruption events [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioSessionWasInterrupted:) name:AVAudioSessionInterruptionNotification object:session]; } // Listen to audio interruption events - (void)audioSessionWasInterrupted:(NSNotification *)notification { //When interrupted if (AVAudioSessionInterruptionTypeBegan == [notification.userInfo[AVAudioSessionInterruptionTypeKey] intValue]) { [_streamer pause]; UIButton *btn = (UIButton *)[self.view viewWithTag:2000]; [btn setBackgroundImage:[UIImage imageNamed:@"music_stop_icon"] forState:UIControlStateNormal]; } else if (AVAudioSessionInterruptionTypeEnded == [notification.userInfo[AVAudioSessionInterruptionTypeKey] intValue]) { } } // Pause playback after unplugging headphones -(void)routeChange:(NSNotification *)notification{ NSDictionary *dic=notification.userInfo; int changeReason= [dic[AVAudioSessionRouteChangeReasonKey] intValue]; //AVAudioSessionRouteChangeReasonOldDeviceUnavailable indicates that the old output is unavailable if (changeReason==AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { AVAudioSessionRouteDescription *routeDescription=dic[AVAudioSessionRouteChangePreviousRouteKey]; AVAudioSessionPortDescription *portDescription= [routeDescription.outputs firstObject]; //Pause if the original device is headphones if ([portDescription.portType isEqualToString:@"Headphones"]) { [_streamer pause]; UIButton *btn = (UIButton *)[self.view viewWithTag:2000]; [btn setBackgroundImage:[UIImage imageNamed:@"music_stop_icon"] forState:UIControlStateNormal]; } } } //Display music on lock screen (This method can be called when playing to pass values) - (void)setupLockScreenInfoWithSing:(NSString *)sign WithSigner:(NSString *)signer WithImage:(UIImage *)image { // 1.GetLockScreenCenter MPNowPlayingInfoCenter *playingInfoCenter = [MPNowPlayingInfoCenter defaultCenter]; //Initialize a dictionary to store music information NSMutableDictionary *playingInfoDict = [NSMutableDictionary dictionary]; // 2Set the song name if (sign) { [playingInfoDict setObject:sign forKey:MPMediaItemPropertyAlbumTitle]; } // Set the artist name if (signer) { [playingInfoDict setObject:signer forKey:MPMediaItemPropertyArtist]; } // 3Set the cover image //UIImage *image = [self getMusicImageWithMusicId:self.currentModel]; if (image) { MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:image]; [playingInfoDict setObject:artwork forKey:MPMediaItemPropertyArtwork]; } // 4Set the total duration of the song //[playingInfoDict setObject:self.currentModel.detailDuration forKey:MPMediaItemPropertyPlaybackDuration]; //Assign music information to the nowPlayingInfo property of the lock screen center playingInfoCenter.nowPlayingInfo = playingInfoDict; // 5.Enable remote interaction [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; } //Lock screen operation - (void)remoteControlReceivedWithEvent:(UIEvent *)receivedEvent { if (receivedEvent.type == UIEventTypeRemoteControl) { UIButton *sender = (UIButton *)[self.view viewWithTag:2000]; switch (receivedEvent.subtype) {//Determine if it is remote control case UIEventSubtypeRemoteControlPause: [[HYNEntertainmentController sharedInstance].streamer pause]; [sender setBackgroundImage:[UIImage imageNamed:@"music_stop_icon"] forState:UIControlStateNormal]; break; case UIEventSubtypeRemoteControlStop: break; case UIEventSubtypeRemoteControlPlay: [[HYNEntertainmentController sharedInstance].streamer play]; [sender setBackgroundImage:[UIImage imageNamed:@"music_play_icon"] forState:UIControlStateNormal]; break; case UIEventSubtypeRemoteControlTogglePlayPause: break; case UIEventSubtypeRemoteControlNextTrack: break; case UIEventSubtypeRemoteControlPreviousTrack: break; default: break; } } }
Overall image:
The above image shows the unplayed state
The above image shows the playing state
The above image shows the locked screen state
There should be nothing more to add, let's pause here temporarily. If there are any deficiencies, you can discuss them in the comment area below. Thank you for your support of the Yelling Tutorial.