本文主要分享在软通动力扬帆系列“竞”OpenHarmony开发板上运行一个Js版本的简单的音乐播放器案例,实现播放暂停及切换上下歌曲。
1、新建工程
选择OpenHarmony中Empty Ability如图
提入Project name,Bundle Name,Compile SDK:8,Language:JS 如下图
2、页面的实现
新建 entry\src\main\js\MainAbility\common\images并将页面上的播放、暂停、上一曲、下一曲图片资源放到目录下
src/main/js/MainAbility/pages/index/index.hml源码如下:
<div class="container" onswipe="touchMove">
<text class="title">
{{ title }}
</text>
<div class="div-but">
<div class="btn">
<image class="btn-image" src="common/images/previous.png" on:click="onPreviousClick"></image>
</div>
<div class="btn">
<image class="btn-image" id="pauseId" src="{{ audioUrl }}" on:click="onPlayClick"></image>
</div>
<div class="btn">
<image class="btn-image" src="common/images/next.png" on:click="onNextClick"></image>
</div>
</div>
</div>
src/main/js/MainAbility/pages/index/index.css源码如下:
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
}
.title {
font-size: 60px;
text-align: center;
width: 100%;
height: 40%;
margin: 10px;
}
.div-but {
width: 100%;
height: 50%;
flex-direction: row;
justify-content: space-around;
}
.btn {
width: 100px;
height: 100px;
}
.btn-image {
object-fit: contain;
}
@media screen and (orientation: landscape) {
.title {
font-size: 60px;
}
}
@media screen and (device-type: tablet) and (orientation: landscape) {
.title {
font-size: 100px;
}
}
3、控制功能
新建entry\src\main\js\MainAbility\common\PlayerModel.js文件,内容如下
import media from '@ohos.multimedia.media';
import fileIo from '@ohos.fileio'
export
class Playlist {
constructor() {
}
audioFiles = [];
}
export
class Song {
constructor(name, fileUri, duration) {
this.name = name;
this.fileUri = fileUri;
this.duration = duration;
}
}
export default class PlayerModel {
isPlaying = false;
playlist = new Playlist;
index;
#player;
#statusChangedListener;
#playingProgressListener;
#intervalID;
#currentTimeMs = 0;
constructor() {
this.#player = media.createAudioPlayer();
this.initAudioPlayer()
console.info('MusicPlayer[PlayerModel] createAudioPlayer=' + this.#player);
}
initAudioPlayer() {
console.info('MusicPlayer[PlayerModel] initAudioPlayer begin');
this.#player.on('error', () => {
console.error(`MusicPlayer[PlayerModel] player error`);
});
this.#player.on('finish', () => {
console.log('MusicPlayer[PlayerModel] finish() callback is called');
this.seek(0)
this.notifyPlayingStatus(false);
});
this.#player.on('timeUpdate', () => {
console.log('MusicPlayer[PlayerModel] timeUpdate() callback is called ')
});
console.info('MusicPlayer[PlayerModel] initAudioPlayer end');
}
release() {
if (typeof (this.#player) != 'undefined') {
console.info('MusicPlayer[PlayerModel] player.release begin');
this.#player.release();
console.info('MusicPlayer[PlayerModel] player.release end');
this.#player = undefined;
}
}
restorePlayingStatus(status, callback) {
console.info('MusicPlayer[PlayerModel] restorePlayingStatus ' + JSON.stringify(status));
for (var i = 0; i < this.playlist.audioFiles.length; i++) {
if (this.playlist.audioFiles[i].fileUri === status.uri) {
console.info('MusicPlayer[PlayerModel] restore to index ' + i);
this.preLoad(i, () => {
this.play(status.seekTo, status.isPlaying);
console.info('MusicPlayer[PlayerModel] restore play status');
callback(i);
});
return;
}
}
console.warn('MusicPlayer[PlayerModel] restorePlayingStatus failed');
callback(-1);
}
getPlaylist(callback) {
console.info('MusicPlayer[PlayerModel] generatePlayList');
console.info('MusicPlayer[PlayerModel] getAudioAssets begin');
this.playlist = new Playlist();
this.playlist.audioFiles = [];
this.playlist.audioFiles[0] = new Song('dynamic.wav', 'system/etc/dynamic.wav', 0);
this.playlist.audioFiles[1] = new Song('demo.wav', 'system/etc/demo.wav', 0);
callback()
console.info('MusicPlayer[PlayerModel] getAudioAssets end');
}
setOnStatusChangedListener(callback) {
this.#statusChangedListener = callback;
}
setOnPlayingProgressListener(callback) {
this.#playingProgressListener = callback;
}
notifyPlayingStatus(isPlaying) {
this.isPlaying = isPlaying;
this.#statusChangedListener(this.isPlaying);
console.log('MusicPlayer[PlayerModel] notifyPlayingStatus isPlaying=' + isPlaying + ' intervalId=' + this.#intervalID);
if (isPlaying) {
if (typeof (this.#intervalID) === 'undefined') {
let self = this;
this.#intervalID = setInterval(() => {
if (typeof (self.#playingProgressListener) != "undefined" && self.#playingProgressListener != null) {
var timeMs = self.#player.currentTime;
this.#currentTimeMs = timeMs;
if (typeof (timeMs) === 'undefined') {
timeMs = 0;
}
console.log('MusicPlayer[PlayerModel] player.currentTime=' + timeMs);
self.#playingProgressListener(timeMs);
}
}, 500);
console.log('MusicPlayer[PlayerModel] set update interval ' + this.#intervalID);
}
} else {
this.cancelTimer();
}
}
cancelTimer() {
if (typeof (this.#intervalID) != 'undefined') {
console.log('MusicPlayer[PlayerModel] clear update interval ' + this.#intervalID);
clearInterval(this.#intervalID);
this.#intervalID = undefined;
}
}
preLoad(index, callback) {
console.info('MusicPlayer[PlayerModel] preLoad ' + index + "/" + this.playlist.audioFiles.length);
if (index < 0 || index >= this.playlist.audioFiles.length) {
console.error('MusicPlayer[PlayerModel] preLoad ignored');
return 0;
}
this.index = index;
let uri = this.playlist.audioFiles[index].fileUri
fileIo.open(uri, (err, fdNumber) => {
let fdPath = 'fd://'
let source = fdPath + fdNumber
if (typeof (source) === 'undefined') {
console.error('MusicPlayer[PlayerModel] preLoad ignored, source=' + source);
return;
}
console.info('MusicPlayer[PlayerModel] preLoad ' + source + ' begin');
console.info('MusicPlayer[PlayerModel] state=' + this.#player.state);
let self = this;
if (source === this.#player.src && this.#player.state != 'idle') {
console.info('MusicPlayer[PlayerModel] preLoad finished. src not changed');
callback();
} else if (this.#player.state === 'idle') {
this.#player.on('dataLoad', () => {
console.info('MusicPlayer[PlayerModel] dataLoad callback, state=' + self.#player.state);
callback();
});
console.info('MusicPlayer[PlayerModel] player.src=' + source);
this.#player.src = source;
} else {
this.notifyPlayingStatus(false);
this.cancelTimer();
console.info('MusicPlayer[PlayerModel] player.reset');
self.#player.reset();
console.info('MusicPlayer[PlayerModel] player.reset done, state=' + self.#player.state);
self.#player.on('dataLoad', () => {
console.info('MusicPlayer[PlayerModel] dataLoad callback, state=' + self.#player.state);
callback();
});
console.info('MusicPlayer[PlayerModel] player.src=' + source);
self.#player.src = source;
}
console.info('MusicPlayer[PlayerModel] preLoad ' + source + ' end');
})
}
getDuration() {
console.info('MusicPlayer[PlayerModel] getDuration index=' + this.index);
if (this.playlist.audioFiles[this.index].duration > 0) {
return this.playlist.audioFiles[this.index].duration;
}
console.info('MusicPlayer[PlayerModel] getDuration state=' + this.#player.state);
if (this.#player.state === 'idle') {
console.warn('MusicPlayer[PlayerModel] getDuration ignored, player.state=' + this.#player.state);
return 0;
}
this.playlist.audioFiles[this.index].duration = Math.min(this.#player.duration, 97615);
console.info('MusicPlayer[PlayerModel] getDuration player.src=' + this.#player.src + ", player.duration=" + this.playlist.audioFiles[this.index].duration);
return this.playlist.audioFiles[this.index].duration;
}
getCurrentMs() {
return this.#currentTimeMs;
}
play(seekTo, startPlay) {
console.info('MusicPlayer[PlayerModel] play seekTo=' + seekTo + ', startPlay=' + startPlay);
this.notifyPlayingStatus(startPlay);
if (startPlay) {
if (seekTo < 0 && this.#currentTimeMs > 0) {
console.info('MusicPlayer[PlayerModel] pop seekTo=' + this.#currentTimeMs);
seekTo = this.#currentTimeMs;
}
let self = this;
this.#player.on('play', (err, action) => {
if (err) {
console.error(`MusicPlayer[PlayerModel] error returned in play() callback`);
return;
}
console.log('MusicPlayer[PlayerModel] play() callback entered, player.state=' + self.#player.state);
if (seekTo > 0) {
self.seek(seekTo);
}
});
console.info('MusicPlayer[PlayerModel] call player.play');
this.#player.play();
console.info('MusicPlayer[PlayerModel] player.play called player.state=' + this.#player.state);
} else if (seekTo > 0) {
this.#playingProgressListener(seekTo);
this.#currentTimeMs = seekTo;
console.info('MusicPlayer[PlayerModel] stash seekTo=' + this.#currentTimeMs);
}
}
pause() {
if (!this.isPlaying) {
console.info('MusicPlayer[PlayerModel] pause ignored, isPlaying=' + this.isPlaying);
return;
}
this.notifyPlayingStatus(false);
console.info('MusicPlayer[PlayerModel] call player.pause');
this.#player.pause();
console.info('MusicPlayer[PlayerModel] player.pause called, player.state=' + this.#player.state);
}
seek(ms) {
this.#currentTimeMs = ms;
if (this.isPlaying) {
console.log('MusicPlayer[PlayerModel] player.seek ' + ms);
this.#player.seek(ms);
} else {
console.log('MusicPlayer[PlayerModel] stash seekTo=' + ms);
}
}
stop() {
if (!this.isPlaying) {
console.info('MusicPlayer[PlayerModel] stop ignored, isPlaying=' + this.isPlaying);
return;
}
this.notifyPlayingStatus(false);
console.info('MusicPlayer[PlayerModel] call player.stop');
this.#player.stop();
console.info('MusicPlayer[PlayerModel] player.stop called, player.state=' + this.#player.state);
}
}
entry\src\main\js\MainAbility\pages\index\index.js修改如下
import audio from '@ohos.multimedia.audio'
import PlayerModel from '../../common/PlayerModel.js';
export default {
data: {
title: '',
audioUrl: '',
index: 0,
audioPlayer: null,
isSwitching: false,
playerModel: new PlayerModel(),
audioManager: null,
volume: 0
},
onInit() {
this.audioUrl = 'common/images/pause.png';
this.playerModel.setOnStatusChangedListener((isPlaying) => {
console.info('MusicPlayer[IndexPage] on player status changed, isPlaying=' + isPlaying + ', refresh ui');
this.playerModel.setOnPlayingProgressListener(() => {
console.info('MusicPlayer[IndexPage] setOnPlayingProgressListener');
});
if (isPlaying) {
this.audioUrl = 'common/images/play.png';
} else {
this.audioUrl = 'common/images/pause.png';
}
});
this.playerModel.getPlaylist(() => {
console.info('MusicPlayer[IndexPage] on playlist generated, refresh ui');
});
this.title = this.playerModel.playlist.audioFiles[this.index].name;
this.audioManager = audio.getAudioManager();
},
onPlayClick() {
if (this.isSwitching) {
console.info('MusicPlayer[IndexPage] onPlayClick ignored, isSwitching');
return;
}
console.info('MusicPlayer[IndexPage] onPlayClick, isPlaying=' + this.playerModel.isPlaying);
if (this.playerModel.isPlaying) {
this.playerModel.pause();
} else {
this.playerModel.preLoad(this.index, () => {
this.playerModel.play(-1, true);
});
}
},
onPreviousClick() {
if (this.isSwitching) {
console.info('MusicPlayer[IndexPage] onPreviousClick ignored, isSwitching');
return;
}
console.info('MusicPlayer[IndexPage] onPreviousClick');
this.index--;
if (this.index < 0 && this.playerModel.playlist.audioFiles.length >= 1) {
this.index = this.playerModel.playlist.audioFiles.length - 1;
}
this.currentProgress = 0;
this.isSwitching = true;
let self = this;
this.playerModel.preLoad(this.index, () => {
self.playerModel.play(0, true);
self.isSwitching = false;
});
this.title = this.playerModel.playlist.audioFiles[this.index].name;
},
onNextClick() {
if (this.isSwitching) {
console.info('MusicPlayer[IndexPage] onNextClick ignored, isSwitching');
return;
}
console.info('MusicPlayer[IndexPage] onNextClick');
this.index++;
if (this.index >= this.playerModel.playlist.audioFiles.length) {
this.index = 0;
}
this.currentProgress = 0;
this.isSwitching = true;
let self = this;
this.playerModel.preLoad(this.index, () => {
self.playerModel.play(0, true);
self.isSwitching = false;
});
this.title = this.playerModel.playlist.audioFiles[this.index].name;
}
}
横屏预览效果如图片
4.播放测试
将开发板上的"SPK"接上喇叭,
将hap推送到开发板上,执行播放暂停,切换上下曲,实际显示效果如图:
主要源码整理如附件:
*附件:JsMusicPlayer.zip
演示视频见文末