Angular2 Service實現(xiàn)簡單音樂播放器服務(wù)
引言
如果說組件系統(tǒng)(Component)是ng2應(yīng)用的軀體,那把服務(wù)(Service)認(rèn)為是流通于組件之間并為其帶來生機(jī)的血液再合適不過了。組件間通信的其中一種優(yōu)等選擇就是使用服務(wù),在ng1里就有了廣泛使用,而ng2保持了服務(wù)的全部特性,包括其全局單例與依賴注入。今天就來實踐一下ng2的服務(wù)(Service)這一利器,來實現(xiàn)一個簡單的音樂播放器,重點(diǎn)在于使用服務(wù)來進(jìn)行音頻的播放控制與全局范圍的調(diào)用。
一、基本項目準(zhǔn)備:
考慮到音頻播放是個比較通用的服務(wù),決定將其創(chuàng)建為一個單獨(dú)的模塊AudioModule,并且在里面新增音頻服務(wù)主文件audio.service.ts,通用的音頻控制中心組件audio-studio.component.ts,作為輔助的TS接口文件play-data.model.ts與audio.model.ts。
最終項目音頻部分的目錄結(jié)構(gòu)如圖所示:

二、創(chuàng)建服務(wù):
ng2的服務(wù),照官網(wǎng)的說法來解釋,其實只是個帶有Injectable裝飾器的類而已,沒有其他任何特殊的定義,所以非常簡單,不過定義如此簡單的服務(wù)卻可以完成非常多酷炫的功能。
在TypeScript下定義變量有了public與private的訪問級區(qū)分,所以定義服務(wù)通常套路就是,定義服務(wù)內(nèi)使用的私有變量,在constructor構(gòu)造函數(shù)中進(jìn)行初始化操作,定義共有方法給服務(wù)的消費(fèi)者使用。
專注于音頻播放服務(wù)的場景,我們需要的私有變量有:
1.音頻對象
①用于通過JS進(jìn)行H5音頻的播放控制
2.播放列表數(shù)據(jù)
①服務(wù)內(nèi)部使用的播放列表概念,實際播放音頻時都是從此列表中播放音頻,服務(wù)的消費(fèi)者可以調(diào)用接口來操作此列表
3.正在播放音頻的參數(shù)
①音頻時長,當(dāng)前進(jìn)度以及播放模式(隨機(jī)播放之類)等
4.播放時的輪詢監(jiān)聽變量
①用于音頻播放過程中自動啟動輪詢,定時(每秒)更新播放參數(shù),當(dāng)音頻暫?;蛲V箷r取消此監(jiān)聽
服務(wù)初始化時需要做的事情有:
1.創(chuàng)建音頻對象
①可直接使用document.createElement('audio'),但不需要將其添加到DOM中。
②后續(xù)的播放控制均使用此對象來操作。
2.初始化私有變量
①私有變量中播放列表是一個數(shù)組,成員的參數(shù)使用audio.model.ts來規(guī)范化,
②必須包含一個Url參數(shù)存放播放源,以及其他可選參數(shù)
③相同的播放參數(shù)也用一個play-data.model.ts來規(guī)范化
3.給音頻添加onplay、onpause、onend等播放事件的監(jiān)聽
此服務(wù)提供的公有接口包括:
1. Toggle(audio)
①判斷傳入的音頻是否已在列表中,已存在則播放或暫停,若不存在則添加進(jìn)來并播放
2. Add()
①僅添加音頻到列表中
3. Remove()
①移除音頻出播放列表,需要考慮好移除后對播放隊列的影響,比如是否是正在播放的音頻被移除等等
4. Next()
5. Prev()
上一曲與下一曲操作,需要考慮到播放模式
6. Skip()
進(jìn)行播放進(jìn)度的跳轉(zhuǎn)
7. PlayList()
8. PlayData()
①用于暴露服務(wù)所維護(hù)的兩個數(shù)據(jù)(播放列表與播放參數(shù)),在指令中都是通過這兩個接口來呈現(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);
}
}
/**
* 移除列表中指定索引的音頻
* 若移除的就是正在播放的音頻則自動播放新的同索引音頻,不存在此索引則遞減
* 若只剩這一條音頻了則停止播放并移除
* @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)致的用戶體驗
*/
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 ]
})
接下來要實現(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方法添加的音頻如果是列表中僅有的一條音頻則會直接播放,所以如此添加兩條音頻會直接播放第一條音頻。
再在組件內(nèi)實現(xiàn)一個Skip方法用于進(jìn)度控制:
public Skip(e) {
this.audio.Skip(e.layerX /
document.getElementById('audio-total').getBoundingClientRect().width);
}
現(xiàn)在運(yùn)行項目:


音頻播放器的樣式是崩塌的...因為這個組件是筆者另一個項目中直接copy過來了,在此demo項目中還沒加上移動端rem適配,尷尬,不過大概的效果是展現(xiàn)出來了。
完整項目代碼下載:angular2-demo_jb51.rar
四、總結(jié):
總的來說ng2的服務(wù)光使用來說難度不高,關(guān)鍵在于如何來完美發(fā)揮服務(wù)的特性,來做數(shù)據(jù)共享傳遞,以及封裝網(wǎng)絡(luò)請求等都是很好的選擇。另外本文沒有專門去講服務(wù)的一些問題點(diǎn),但使用服務(wù)還是有一些需要注意的地方的,比如只能在單個模塊中的provider中聲明,盡量保持全局單例,以及在懶加載模塊中會創(chuàng)建子注入器等,實際項目中還是要解決一些問題的。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Angular Excel 導(dǎo)入與導(dǎo)出的實現(xiàn)代碼
這篇文章主要介紹了Angular Excel 導(dǎo)入與導(dǎo)出的實現(xiàn)代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-04-04
使用AngularJS對表單提交內(nèi)容進(jìn)行驗證的操作方法
AngularJS是一款優(yōu)秀的前端JS框架,已經(jīng)被用于Google的多款產(chǎn)品當(dāng)中。下面通過本文給大家分享使用AngularJS對表單提交內(nèi)容進(jìn)行驗證的操作方法,需要的的朋友參考下吧2017-07-07
使用Angular 6創(chuàng)建各種動畫效果的方法
Angular能夠讓我們創(chuàng)建出具有原生表現(xiàn)效果的動畫。我們將通過本文學(xué)習(xí)到如何使用Angular 6來創(chuàng)建各種動畫效果。在此,我們將使用Visual Studio Code來進(jìn)行示例演示。感興趣的朋友跟隨小編一起看看吧2018-10-10
BootStrap+Angularjs+NgDialog實現(xiàn)模式對話框
在完成一個后臺管理系統(tǒng)時,需要用表格顯示注冊用戶的信息。但是用戶地址太長了,不好顯示。所以想做一個模式對話框,點(diǎn)擊詳細(xì)地址按鈕時,彈出對話框,顯示地址。下面小編給大家分享下實現(xiàn)方法,一起看下吧2016-08-08
Angular 多級路由實現(xiàn)登錄頁面跳轉(zhuǎn)(小白教程)
這篇文章主要介紹了Angular 多級路由實現(xiàn)登錄頁面跳轉(zhuǎn)(小白教程),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11

