iOS Development: Design and implementation of a music player

Source: Internet
Author: User
Tags uikit

GitHub Address: Https://github.com/wzpziyi1/MusicPlauer

This Medo, the main function of the song play has been realized. Next song, previous song, pause, according to the progress of the song to dynamically scroll the lyrics, the currently playing lyrics to enlarge the display, drag the progress bar, the song followed the changes, and using time Profiler is optimized, but also using Xctest to several major classes of unit testing.

After the real-time debugging, music can be played in the background, and when the screen is locked, some major song information is displayed.

Home:

Song Internal playback:

When you drag a small progress bar, the song changes as well.

Display the lyrics interface:

This is based on the song's playback to display the corresponding lyrics. Use UITableView to display lyrics, you can manually scroll the interface to see the lyrics behind or in front.
Also, when you drag a progress bar, the lyrics change, and the next and previous songs are still available.

Code Analysis:

preparation stage, first wrote a single audio playback, using this single example to play the music files in the demo, the code is as follows:

#import <Foundation/Foundation.h> #import <AVFoundation/AVFoundation.h> @interface Zyaudiomanager: nsobject+ (instancetype) defaultmanager;//play Music-(Avaudioplayer *) Playingmusic: (NSString *) filename;-(void) Pausemusic: (NSString *) filename;-(void) Stopmusic: (NSString *) filename;//play sound Effects-(void) PlaySound: (NSString *) filename ;-(void) Disposesound: (NSString *) filename, @end #import "ZYAudioManager.h" @interface Zyaudiomanager () @property ( Nonatomic, strong) Nsmutabledictionary *musicplayers; @property (nonatomic, strong) Nsmutabledictionary *soundIDs;@ Endstatic Zyaudiomanager *_instance = nil, @implementation zyaudiomanager+ (void) initialize{//audio Session Avaudiosession *        session = [Avaudiosession sharedinstance];        Set Session type (play type, play mode, will automatically stop other music playback) [session Setcategory:avaudiosessioncategoryplayback Error:nil]; Activating the session [session Setactive:yes Error:nil];}    + (instancetype) defaultmanager{static dispatch_once_t Oncetoken; Dispatch_once (&oncetoken, ^{_instance = [[Self alloc] init];    }); return _instance;}        -(instancetype) init{__block zyaudiomanager *temp = self;    Static dispatch_once_t Oncetoken; Dispatch_once (&oncetoken, ^{if (temp = [Super init])! = nil) {_musicplayers = [Nsmutabledictionar            Y dictionary];        _soundids = [Nsmutabledictionary dictionary];    }    });    Self = temp; return self;}    + (Instancetype) Allocwithzone: (struct _nszone *) zone{static dispatch_once_t Oncetoken;    Dispatch_once (&oncetoken, ^{_instance = [Super Allocwithzone:zone];    }); return _instance;}        Play Music-(Avaudioplayer *) Playingmusic: (NSString *) filename{if (filename = = Nil | | filename.length = = 0) return nil;      Avaudioplayer *player = Self.musicplayers[filename];                First, query whether the object caches if (!player) {Nsurl *url = [[NSBundle mainbundle] Urlforresource:filename Withextension:nil];                if (!url) return nil; Player = [[Avaudioplayer alloc] INitwithcontentsofurl:url Error:nil]; if (![                Player Preparetoplay]) return nil;            Self.musicplayers[filename] = player; Object is newly created, it is cached once} if (![    Player IsPlaying]) {//If it is not playing, start playing, if it is playing, then do not need to change what [player play]; } return player;        -(void) Pausemusic: (NSString *) filename{if (filename = = Nil | | filename.length = = 0) return;        Avaudioplayer *player = Self.musicplayers[filename];    if ([player isplaying]) {[player pause];        }}-(void) Stopmusic: (NSString *) filename{if (filename = = Nil | | filename.length = = 0) return;        Avaudioplayer *player = Self.musicplayers[filename];        [Player stop]; [Self.musicplayers removeobjectforkey:filename];}        Play sound-(void) PlaySound: (NSString *) filename{if (!filename) return;        Remove the corresponding sound effect ID systemsoundid soundid = (int) [self.soundids[filename] unsignedlongvalue]; if (!soundid) {Nsurl *url = [[NSBundle mainbundle] Urlforresource:filename Withextension:nil];                if (!url) return;                Audioservicescreatesystemsoundid (__bridge cfurlref) (URL), &soundid);    Self.soundids[filename] = @ (Soundid); }//Play Audioservicesplaysystemsound (Soundid);}            Destroy sound-(void) Disposesound: (NSString *) filename{if (!filename) return;        Systemsoundid soundid = (int) [self.soundids[filename] unsignedlongvalue];                if (Soundid) {audioservicesdisposesystemsoundid (soundid);    [Self.soundids Removeobjectforkey:filename]; The sound is destroyed, then the corresponding object should be removed from the cache}} @end

is a single example of the design, and not much difficulty. I used a dictionary to install the played songs, so if it is paused, and then start playing, directly in the cache to load. However, if you do not notice, in the Stopmusic: (NSString *) FileName This method, do not remove from the dictionary has stopped the song, then you play the song again, you will play on the original progress of the play continues. During the encoding process, I encountered this bug and found that when I switched songs (previous, next) I called the Stopmusic method, but because I didn't remove it from the dictionary, it always started playing from the last progress, not from the beginning.

If you want to play the songs in the background on the real machine, in addition to doing the corresponding actions in Appdelegate and plist, you also have to set the playback mode to: Avaudiosessioncategoryplayback. In particular, it is important to note here that when I debug on the simulator, this mode is not set and can be played in the background, but it is not on the real machine. Later on the stackoverflow found the corresponding answer, need to set the playback mode.

This singleton class, which is very important throughout the demo, is to ensure that it is error-free, so I wrote this class of xctest for unit testing with the following code:

#import <XCTest/XCTest.h> #import "ZYAudioManager.h" #import <AVFoundation/AVFoundation.h> @interface Zyaudiomanagertests:xctestcase@property (nonatomic, strong) Avaudioplayer *player; @endstatic nsstring *_fileName = @ "    10405520.mp3 "; @implementation zyaudiomanagertests-(void) setup {[Super setUp]; Put Setup code here. This method was called before the invocation of each test method in the class.} -(void) TearDown {//Put TearDown code here.    This method was called after the invocation for each test method in the class. [Super TearDown];}    -(void) Testexample {//This was an example of a functional test case. Use Xctassert and related functions to verify your tests produce the correct results.}        /** * Test is a singleton, to test */-(void) testaudiomanagersingle{nsmutablearray *managers = [nsmutablearray array] under concurrency conditions;        dispatch_group_t group = Dispatch_group_create (); Dispatch_group_async (Group, Dispatch_get_global_queue (Dispatch_queue_priority_default, 0), ^{Zyaudiomanager *tempmanager = [[Zyaudiomanager alloc] init];    [Managers Addobject:tempmanager];        }); Dispatch_group_async (Group, Dispatch_get_global_queue (Dispatch_queue_priority_default, 0), ^{ZYAudioManager *tempM        Anager = [[Zyaudiomanager alloc] init];    [Managers Addobject:tempmanager];        }); Dispatch_group_async (Group, Dispatch_get_global_queue (Dispatch_queue_priority_default, 0), ^{ZYAudioManager *tempM        Anager = [[Zyaudiomanager alloc] init];    [Managers Addobject:tempmanager];        }); Dispatch_group_async (Group, Dispatch_get_global_queue (Dispatch_queue_priority_default, 0), ^{ZYAudioManager *tempM        Anager = [[Zyaudiomanager alloc] init];    [Managers Addobject:tempmanager];        }); Dispatch_group_async (Group, Dispatch_get_global_queue (Dispatch_queue_priority_default, 0), ^{ZYAudioManager *tempM        Anager = [[Zyaudiomanager alloc] init];    [Managers Addobject:tempmanager];        }); ZyaudioManager *managerone = [Zyaudiomanager Defaultmanager]; Dispatch_group_notify (Group, Dispatch_get_global_queue (Dispatch_queue_priority_default, 0), ^{[managers en umerateobjectsusingblock:^ (Zyaudiomanager *obj, Nsuinteger idx, BOOL * _nonnull stop) {xctassertequal (ManagerO        NE, obj, @ "Zyaudiomanager is not a single");            }]; });}     /** * Test to play Music */-(void) testplayingmusic{self.player = [[Zyaudiomanager Defaultmanager] playingmusic:_filename]; Xctasserttrue (self.player.playing, @ "Zyaudiomanager is not Playingmusic");} /** * Test if normal stop music */-(void) teststopmusic{if (Self.player = = nil) {self.player = [[Zyaudiomanager Defaultman    Ager] playingmusic:_filename];        } if (self.player.playing = = NO) [Self.player play];    [[Zyaudiomanager Defaultmanager] stopmusic:_filename]; Xctassertfalse (self.player.playing, @ "Zyaudiomanager is not Stopmusic");} /** * Test whether the music */-(void) testpausemusic{if (Self.player = = NI) can be paused normallyL) {self.player = [[Zyaudiomanager Defaultmanager] playingmusic:_filename];    } if (self.player.playing = = NO) [Self.player play];    [[Zyaudiomanager Defaultmanager] pausemusic:_filename]; Xctassertfalse (self.player.playing, @ "Zyaudiomanager is not Pausemusic");} @end

It should be noted that the singleton to test under concurrent conditions, I am using Dispatch_group, mainly considering that it is necessary to wait for all concurrent ends in order to compare the results, otherwise there may be an error. For example, in the concurrency condition, the x thread has been executed, its corresponding a object already has the value, and the Y thread has not started initialization, it corresponds to the B object or nil, in order to avoid this condition, I use Dispatch_group to wait for all concurrent end, then to do the corresponding judgment.

Home Controller's Code:

#import "ZYMusicViewController.h" #import "ZYPlayingViewController.h" #import "ZYMusicTool.h" #import "ZYMusic.h" # Import "ZYMusicCell.h" @interface Zymusicviewcontroller () @property (nonatomic, strong) Zyplayingviewcontroller * PLAYINGVC; @property (nonatomic, assign) int currentindex; @end @implementation zymusicviewcontroller-( Zyplayingviewcontroller *) playingvc{if (_PLAYINGVC = = nil) {_PLAYINGVC = [[Zyplayingviewcontroller alloc] Init    withnibname:@ "Zyplayingviewcontroller" bundle:nil]; } return _PLAYINGVC;}        -(void) viewdidload {[Super viewdidload]; [Self setupnavigation];} -(void) setupnavigation{self.navigationItem.title = @ "Music player";} #pragma mark----tableviewdatasource-(Nsinteger) Numberofsectionsintableview: (UITableView *) TableView {return 1;} -(Nsinteger) TableView: (UITableView *) TableView numberofrowsinsection: (nsinteger) Section {return [Zymusictool musics ].count;} -(UITableViewCell *) TableView: (UITableView *) TableView Cellforrowatindexpath: (nsindexPath *) indexpath{Zymusiccell *cell = [Zymusiccell Musiccellwithtableview:tableview];    Cell.music = [Zymusictool musics][indexpath.row]; return cell;} #pragma mark----tableviewdelegate-(cgfloat) TableView: (UITableView *) TableView Heightforrowatindexpath: ( Nsindexpath *) indexpath{return 70;} -(void) TableView: (UITableView *) TableView Didselectrowatindexpath: (Nsindexpath *) indexpath{[TableView        Deselectrowatindexpath:indexpath Animated:yes];        [Zymusictool Setplayingmusic:[zymusictool Musics][indexpath.row];    Zymusic *premusic = [Zymusictool musics][self.currentindex];    premusic.playing = NO;    Zymusic *music = [Zymusictool musics][indexpath.row];    music.playing = YES;                            Nsarray *indexpaths = @[[Nsindexpath indexPathForItem:self.currentIndex insection:0],    Indexpath];        [Self.tableview reloadrowsatindexpaths:indexpaths Withrowanimation:uitableviewrowanimationnone]; SelF.currentindex = (int) Indexpath.row; [SELF.PLAYINGVC show];} @end  

The key point to talk about is the implementation of this interface:

Here do more detail control, specific in the code has a corresponding description. The main thing is to say, in the implementation of the playback progress of the problem encountered in the drag.

To control the movement of the progress bar, I used the Nstimer, added a timer, and did the appropriate removal where it was not needed.
When developing here, there is a problem that when I drag the slider, I find that the progress of the song playback is not correct. You can see in the code:

Get moving distance    cgpoint point = [Sender TranslationInView:sender.view];    Empty the translation to avoid overlapping    [sender Settranslation:cgpointzero InView:sender.view];

When using translation, be sure to remember that after each processing, be sure to empty the translation, lest it constantly superimposed.

I'm using Zylrcview to show the lyrics interface, and it's important to note that it inherits from Uiimageview, so set the Userinteractionenabled property to Yes.
Code:

#import <UIKit/UIKit.h> @interface Zylrcview:uiimageview@property (nonatomic, assign) Nstimeinterval currenttime; @property (nonatomic, copy) NSString *filename; @end #import "ZYLrcView.h" #import "ZYLrcLine.h" #import " ZYLrcCell.h "#import" Uiview+autolayout.h "@interface Zylrcview () <uitableviewdatasource, uitableviewdelegate> @property (nonatomic, weak) UITableView *tableview; @property (nonatomic, strong) Nsmutablearray *lrclines;/** * Record the current display lyrics The index in the array */@property (nonatomic, assign) int currentindex; @end @implementation zylrcview#pragma Mark----setter\ Geter Method-(Nsmutablearray *) lrclines{if (_lrclines = = nil) {_lrclines = [Zylrcline lrcLinesWithFileName:self.fi    Lename]; } return _lrclines;}    -(void) Setfilename: (NSString *) filename{if ([_filename Isequaltostring:filename]) {return;    } _filename = [fileName copy];    [_lrclines removeallobjects];    _lrclines = nil; [Self.tableview Reloaddata];} -(void) Setcurrenttime: (nstimeinterval) Currenttime{if (_currenttime > currenttime) {self.currentindex = 0;        } _currenttime = CurrentTime;    int minute = CURRENTTIME/60;    int second = (int) CurrentTime% 60;    int Msecond = (currenttime-(int) currenttime) * 100; NSString *currenttimestr = [NSString stringwithformat:@ "%02d:%02d.%0        2d ", minute, second, Msecond];        for (int i = Self.currentindex; i < Self.lrcLines.count; i++) {Zylrcline *currentline = self.lrclines[i];        NSString *currentlinetime = currentline.time;                NSString *nextlinetime = nil;            if (i + 1 < self.lrcLines.count) {Zylrcline *nextline = self.lrclines[i + 1];        Nextlinetime = Nextline.time; } if (([Currenttimestr compare:currentlinetime]! = nsorderedascending) && ([Currenttimestr Compare: Nextlinetime] = = nsorderedascending) && (self.currentindex! = i)) {Nsarray *re                       Loadlines = @[              [Nsindexpath IndexPathForItem:self.currentIndex insection:0], [NSINDEXPA            th indexpathforitem:i insection:0];            Self.currentindex = i;                                    [Self.tableview Reloadrowsatindexpaths:reloadlines Withrowanimation:uitableviewrowanimationnone]; [Self.tableview scrolltorowatindexpath:[nsindexpath indexPathForItem:self.currentIndex insection:0] Atscrollpo        Sition:uitableviewscrollpositiontop Animated:yes]; }}} #pragma mark----initialization Method-(Instancetype) initWithFrame: (CGRect) frame{if (self = [Super Initwithframe:frame    ]) {[Self commitinit]; } return self;} -(Instancetype) Initwithcoder: (Nscoder *) adecoder{if (self = [Super Initwithcoder:adecoder]) {[Self commitinit    ]; } return self;}    -(void) commitinit{self.userinteractionenabled = YES;    Self.image = [UIImage imagenamed:@ "28131977_1383101943208"]; Self.contentmOde = Uiviewcontentmodescaletofill;    Self.clipstobounds = YES;    UITableView *tableview = [[UITableView alloc] init];    Tableview.delegate = self;    Tableview.datasource = self;    Tableview.separatorstyle = Uitableviewcellseparatorstylenone;    Tableview.backgroundcolor = [Uicolor Clearcolor];    Self.tableview = TableView;    [Self addsubview:tableview]; [Self.tableview autopinedgestosuperviewedgeswithinsets:uiedgeinsetsmake (0, 0, 0, 0)];} #pragma mark----uitableviewdatasource-(Nsinteger) Numberofsectionsintableview: (UITableView *) tableview{return 1;} -(Nsinteger) TableView: (UITableView *) TableView numberofrowsinsection: (nsinteger) section{return Self.lrcLines.count;} -(UITableViewCell *) TableView: (UITableView *) TableView Cellforrowatindexpath: (Nsindexpath *) indexpath{Zylrccell *    cell = [Zylrccell Lrccellwithtableview:tableview];        Cell.lrcline = Self.lrclines[indexpath.row]; if (Indexpath.row = = self.currentindex) {Cell.textLabel.font = [UifoNT Boldsystemfontofsize:16];    } else{cell.textLabel.font = [Uifont systemfontofsize:13]; } return cell;    -(void) layoutsubviews{[Super Layoutsubviews];    NSLog (@ "++++++++++%@", Nsstringfromcgrect (Self.tableView.frame)); Self.tableView.contentInset = Uiedgeinsetsmake (SELF.FRAME.SIZE.HEIGHT/2, 0, SELF.FRAME.SIZE.HEIGHT/2, 0);} @end

There is nothing to say, the overall idea is to parse the lyrics, the lyrics corresponding to the playing time, in the current playing time of the lyrics one by one corresponding, and then hold a lyrics playing timer, each time to zylrcview incoming songs to play the current time, if, Song of the CurrentTime > The current lyrics play, and less than the next sentence of the playing time, then it is playing the current sentence of the lyrics.

I have done the corresponding optimization, Cadisplaylink generated timer, is every millisecond call trigger once, 1s equals 1000ms, if not do a certain optimization, performance is very poor, after all, a song how also have four or five minutes. Here, I recorded the last line of the lyrics index, then if the normal play, it to find lyrics should be from the previous sentence to play the lyrics in the array of the index start to find, so that the optimization of a lot.

This is the screen under the lock display:

This is the scenario when using the instruments Time Profiler:

There are many other details, not one by one cases.

GitHub Address: Https://github.com/wzpziyi1/MusicPlauer

iOS Development: design and implementation of a music player

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.