Angular2 Service實(shí)現(xiàn)簡(jiǎn)單音樂播放器服務(wù)
引言
如果說組件系統(tǒng)(Component)是ng2應(yīng)用的軀體,那把服務(wù)(Service)認(rèn)為是流通于組件之間并為其帶來生機(jī)的血液再合適不過了。組件間通信的其中一種優(yōu)等選擇就是使用服務(wù),在ng1里就有了廣泛使用,而ng2保持了服務(wù)的全部特性,包括其全局單例與依賴注入。今天就來實(shí)踐一下ng2的服務(wù)(Service)這一利器,來實(shí)現(xiàn)一個(gè)簡(jiǎn)單的音樂播放器,重點(diǎn)在于使用服務(wù)來進(jìn)行音頻的播放控制與全局范圍的調(diào)用。
一、基本項(xiàng)目準(zhǔn)備:
考慮到音頻播放是個(gè)比較通用的服務(wù),決定將其創(chuàng)建為一個(gè)單獨(dú)的模塊AudioModule,并且在里面新增音頻服務(wù)主文件audio.service.ts,通用的音頻控制中心組件audio-studio.component.ts,作為輔助的TS接口文件play-data.model.ts與audio.model.ts。
最終項(xiàng)目音頻部分的目錄結(jié)構(gòu)如圖所示:
二、創(chuàng)建服務(wù):
ng2的服務(wù),照官網(wǎng)的說法來解釋,其實(shí)只是個(gè)帶有Injectable裝飾器的類而已,沒有其他任何特殊的定義,所以非常簡(jiǎn)單,不過定義如此簡(jiǎn)單的服務(wù)卻可以完成非常多酷炫的功能。
在TypeScript下定義變量有了public與private的訪問級(jí)區(qū)分,所以定義服務(wù)通常套路就是,定義服務(wù)內(nèi)使用的私有變量,在constructor構(gòu)造函數(shù)中進(jìn)行初始化操作,定義共有方法給服務(wù)的消費(fèi)者使用。
專注于音頻播放服務(wù)的場(chǎng)景,我們需要的私有變量有:
1.音頻對(duì)象
①用于通過JS進(jìn)行H5音頻的播放控制
2.播放列表數(shù)據(jù)
①服務(wù)內(nèi)部使用的播放列表概念,實(shí)際播放音頻時(shí)都是從此列表中播放音頻,服務(wù)的消費(fèi)者可以調(diào)用接口來操作此列表
3.正在播放音頻的參數(shù)
①音頻時(shí)長(zhǎng),當(dāng)前進(jìn)度以及播放模式(隨機(jī)播放之類)等
4.播放時(shí)的輪詢監(jiān)聽變量
①用于音頻播放過程中自動(dòng)啟動(dòng)輪詢,定時(shí)(每秒)更新播放參數(shù),當(dāng)音頻暫?;蛲V箷r(shí)取消此監(jiān)聽
服務(wù)初始化時(shí)需要做的事情有:
1.創(chuàng)建音頻對(duì)象
①可直接使用document.createElement('audio'),但不需要將其添加到DOM中。
②后續(xù)的播放控制均使用此對(duì)象來操作。
2.初始化私有變量
①私有變量中播放列表是一個(gè)數(shù)組,成員的參數(shù)使用audio.model.ts來規(guī)范化,
②必須包含一個(gè)Url參數(shù)存放播放源,以及其他可選參數(shù)
③相同的播放參數(shù)也用一個(gè)play-data.model.ts來規(guī)范化
3.給音頻添加onplay、onpause、onend等播放事件的監(jiān)聽
此服務(wù)提供的公有接口包括:
1. Toggle(audio)
①判斷傳入的音頻是否已在列表中,已存在則播放或暫停,若不存在則添加進(jìn)來并播放
2. Add()
①僅添加音頻到列表中
3. Remove()
①移除音頻出播放列表,需要考慮好移除后對(duì)播放隊(duì)列的影響,比如是否是正在播放的音頻被移除等等
4. Next()
5. Prev()
上一曲與下一曲操作,需要考慮到播放模式
6. Skip()
進(jìn)行播放進(jìn)度的跳轉(zhuǎn)
7. PlayList()
8. PlayData()
①用于暴露服務(wù)所維護(hù)的兩個(gè)數(shù)據(jù)(播放列表與播放參數(shù)),在指令中都是通過這兩個(gè)接口來呈現(xiàn)數(shù)據(jù)的
服務(wù)的完整代碼如下:
import { Injectable } from '@angular/core'; import { Audio } from './audio.model'; import { PlayData } from './play-data.model'; /** * 音頻服務(wù),只關(guān)心播放列表控制與進(jìn)度控制 * 不提供組件支持,只提供列表控制方法接口及進(jìn)度控制接口 */ @Injectable() export class AudioService { // 主音頻標(biāo)簽 private _audio: HTMLAudioElement; // 當(dāng)前列表中的音頻 private playList: Audio[]; // 當(dāng)前播放的數(shù)據(jù) private playData: PlayData; private listenInterval; /** * 創(chuàng)建新的音頻標(biāo)簽 */ constructor() { this._audio = document.createElement('audio'); this._audio.autoplay = false; this._audio.onplay = () => { let that = this; this.listenInterval = window.setInterval(() => { that.playData.Current = that._audio.currentTime; that.playData.Url = that._audio.src; that.playData.During = that._audio.duration; that.playData.Data = that._audio.buffered && that._audio.buffered.length ? (that._audio.buffered.end(0) || 0) : 0; }, 1000); this.playData.IsPlaying = true; }; this._audio.onended = () => { window.clearInterval(this.listenInterval); this.FillPlayData(); this.playData.IsPlaying = false; }; this._audio.onabort = () => { window.clearInterval(this.listenInterval); this.playData.Current = this._audio.currentTime; this.playData.Url = this._audio.src; this.playData.During = this._audio.duration; this.playData.Data = this._audio.buffered && this._audio.buffered.length ? (this._audio.buffered.end(0) || 0) : 0; this.playData.IsPlaying = false; }; this._audio.onpause = () => { window.clearInterval(this.listenInterval); this.playData.Current = this._audio.currentTime; this.playData.Url = this._audio.src; this.playData.During = this._audio.duration; this.playData.Data = this._audio.buffered && this._audio.buffered.length ? (this._audio.buffered.end(0) || 0) : 0; this.playData.IsPlaying = false; }; this.playData = { Style: 0, Index: 0 }; this.playList = []; } /** * 1.列表中無此音頻則添加并播放 * 2.列表中存在此音頻但未播放則播放 * 3.列表中存在此音頻且在播放則暫停 * @param audio */ public Toggle(audio?: Audio): void { let tryGet = audio ? this.playList.findIndex((p) => p.Url === audio.Url) : this.playData.Index; if (tryGet < 0) { this.playList.push(audio); this.PlayIndex(this.playList.length); } else { if (tryGet === this.playData.Index) { if (this._audio.paused) { this._audio.play(); this.playData.IsPlaying = true; } else { this._audio.pause(); this.playData.IsPlaying = false; } } else { this.PlayIndex(tryGet); } } } /** * 若列表中無此音頻則添加到列表的最后 * 若列表中無音頻則添加后并播放 * @param audio */ public Add(audio: Audio): void { this.playList.push(audio); if (this.playList.length === 1) { this.PlayIndex(0); } } /** * 移除列表中指定索引的音頻 * 若移除的就是正在播放的音頻則自動(dòng)播放新的同索引音頻,不存在此索引則遞減 * 若只剩這一條音頻了則停止播放并移除 * @param index */ public Remove(index: number): void { this.playList.splice(index, 1); if (!this.playList.length) { this._audio.src = ''; } else { this.PlayIndex(index); } } /** * 下一曲 */ public Next(): void { switch (this.playData.Style) { case 0: if (this.playData.Index < this.playList.length) { this.playData.Index++; this.PlayIndex(this.playData.Index); } break; case 1: this.playData.Index = (this.playData.Index + 1) % this.playList.length; this.PlayIndex(this.playData.Index); break; case 2: this.playData.Index = (this.playData.Index + 1) % this.playList.length; this.PlayIndex(this.playData.Index); console.log('暫不考慮隨機(jī)播放將視為列表循環(huán)播放'); break; case 3: this._audio.currentTime = 0; break; default: if (this.playData.Index < this.playList.length) { this.playData.Index++; this.PlayIndex(this.playData.Index); } break; } } /** * 上一曲 */ public Prev(): void { switch (this.playData.Style) { case 0: if (this.playData.Index > 0) { this.playData.Index--; this.PlayIndex(this.playData.Index); } break; case 1: this.playData.Index = (this.playData.Index - 1) < 0 ? (this.playList.length - 1) : (this.playData.Index - 1); this.PlayIndex(this.playData.Index); break; case 2: this.playData.Index = (this.playData.Index - 1) < 0 ? (this.playList.length - 1) : (this.playData.Index - 1); this.PlayIndex(this.playData.Index); console.log('暫不考慮隨機(jī)播放將視為列表循環(huán)播放'); break; case 3: this._audio.currentTime = 0; break; default: if (this.playData.Index > 0) { this.playData.Index--; this.PlayIndex(this.playData.Index); } break; } } /** * 將當(dāng)前音頻跳轉(zhuǎn)到指定百分比進(jìn)度處 * @param percent */ public Skip(percent: number): void { this._audio.currentTime = this._audio.duration * percent; this.playData.Current = this._audio.currentTime; } public PlayList(): Audio[] { return this.playList; } public PlayData(): PlayData { return this.playData; } /** * 用于播放最后強(qiáng)行填滿進(jìn)度條 * 防止播放進(jìn)度偏差導(dǎo)致的用戶體驗(yàn) */ private FillPlayData(): void { this.playData.Current = this._audio.duration; this.playData.Data = this._audio.duration; } /** * 嘗試播放指定索引的音頻 * 索引不存在則嘗試遞增播放,又失敗則遞減播放,又失敗則失敗 * @param index */ private PlayIndex(index: number): void { index = this.playList[index] ? index : this.playList[index + 1] ? (index + 1) : this.playList[index - 1] ? (index - 1) : -1; if (index !== -1) { this._audio.src = this.playList[index].Url; if (this._audio.paused) { this._audio.play(); this.playData.IsPlaying = true; } this.playData.Index = index; } else { console.log('nothing to be play'); } } }
三、使用服務(wù):
接下來要使用服務(wù)了,再ng2中服務(wù)也要依賴具體的模塊,我們得音頻服務(wù)依賴的就是自己的音頻模塊,在模塊的provider列表中配置它:
@NgModule({ imports: [ CommonModule, SharedModule ], declarations: [ AudioStudioComponent ], exports: [ AudioStudioComponent ], providers: [ AudioService ] })
接下來要實(shí)現(xiàn)服務(wù)的消費(fèi)者——AudioStudioComponent 了,步驟如下:
1.在構(gòu)造函數(shù)中注入服務(wù):
constructor(public audio: AudioService) { }
2.使用Add()方法添加音頻:
audio.Add({Url: '/assets/audio/唐人街.mp3', Title: '唐人街-林宥嘉', Cover: '/assets/img/2219A91D.jpg'}); audio.Add({Url: '/assets/audio/自然醒.mp3', Title: '自然醒-林宥嘉', Cover: '/assets/img/336076CD.jpg'});
Add方法添加的音頻如果是列表中僅有的一條音頻則會(huì)直接播放,所以如此添加兩條音頻會(huì)直接播放第一條音頻。
再在組件內(nèi)實(shí)現(xiàn)一個(gè)Skip方法用于進(jìn)度控制:
public Skip(e) { this.audio.Skip(e.layerX / document.getElementById('audio-total').getBoundingClientRect().width); }
現(xiàn)在運(yùn)行項(xiàng)目:
音頻播放器的樣式是崩塌的...因?yàn)檫@個(gè)組件是筆者另一個(gè)項(xiàng)目中直接copy過來了,在此demo項(xiàng)目中還沒加上移動(dòng)端rem適配,尷尬,不過大概的效果是展現(xiàn)出來了。
完整項(xiàng)目代碼下載:angular2-demo_jb51.rar
四、總結(jié):
總的來說ng2的服務(wù)光使用來說難度不高,關(guān)鍵在于如何來完美發(fā)揮服務(wù)的特性,來做數(shù)據(jù)共享傳遞,以及封裝網(wǎng)絡(luò)請(qǐng)求等都是很好的選擇。另外本文沒有專門去講服務(wù)的一些問題點(diǎn),但使用服務(wù)還是有一些需要注意的地方的,比如只能在單個(gè)模塊中的provider中聲明,盡量保持全局單例,以及在懶加載模塊中會(huì)創(chuàng)建子注入器等,實(shí)際項(xiàng)目中還是要解決一些問題的。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Angular Excel 導(dǎo)入與導(dǎo)出的實(shí)現(xiàn)代碼
這篇文章主要介紹了Angular Excel 導(dǎo)入與導(dǎo)出的實(shí)現(xiàn)代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-04-04使用AngularJS制作一個(gè)簡(jiǎn)單的RSS閱讀器的教程
這篇文章主要介紹了使用Angular.js制作一個(gè)簡(jiǎn)單的RSS閱讀器的教程,AngularJS是一個(gè)非常有人氣的JavaScript庫,文中介紹的制作方法主要使用到了FreedReadR模版,需要的朋友可以參考下2015-06-06使用AngularJS對(duì)表單提交內(nèi)容進(jìn)行驗(yàn)證的操作方法
AngularJS是一款優(yōu)秀的前端JS框架,已經(jīng)被用于Google的多款產(chǎn)品當(dāng)中。下面通過本文給大家分享使用AngularJS對(duì)表單提交內(nèi)容進(jìn)行驗(yàn)證的操作方法,需要的的朋友參考下吧2017-07-07使用Angular 6創(chuàng)建各種動(dòng)畫效果的方法
Angular能夠讓我們創(chuàng)建出具有原生表現(xiàn)效果的動(dòng)畫。我們將通過本文學(xué)習(xí)到如何使用Angular 6來創(chuàng)建各種動(dòng)畫效果。在此,我們將使用Visual Studio Code來進(jìn)行示例演示。感興趣的朋友跟隨小編一起看看吧2018-10-10BootStrap+Angularjs+NgDialog實(shí)現(xiàn)模式對(duì)話框
在完成一個(gè)后臺(tái)管理系統(tǒng)時(shí),需要用表格顯示注冊(cè)用戶的信息。但是用戶地址太長(zhǎng)了,不好顯示。所以想做一個(gè)模式對(duì)話框,點(diǎn)擊詳細(xì)地址按鈕時(shí),彈出對(duì)話框,顯示地址。下面小編給大家分享下實(shí)現(xiàn)方法,一起看下吧2016-08-08Angular 多級(jí)路由實(shí)現(xiàn)登錄頁面跳轉(zhuǎn)(小白教程)
這篇文章主要介紹了Angular 多級(jí)路由實(shí)現(xiàn)登錄頁面跳轉(zhuǎn)(小白教程),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11