OpenHarmony开源社区
直播中

soon顺soon

4年用户 306经验值
擅长:嵌入式技术
私信 关注
[经验]

【软通动力鸿湖万联扬帆系列“竞”开发板试用体验】实现一个简单的音乐播放器(Js)

本文主要分享在软通动力扬帆系列“竞”OpenHarmony开发板上运行一个Js版本的简单的音乐播放器案例,实现播放暂停及切换上下歌曲。

1、新建工程

选择OpenHarmony中Empty Ability如图
1.png

提入Project name,Bundle Name,Compile SDK:8,Language:JS 如下图
CreateProject.png

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文件,内容如下

/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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) {
    // generate play list
    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;
}
}

横屏预览效果如图片
Previewer.png

4.播放测试

将开发板上的"SPK"接上喇叭,

SPK接线.jpg
将hap推送到开发板上,执行播放暂停,切换上下曲,实际显示效果如图:

实际显示效果.jpg

主要源码整理如附件:

*附件:JsMusicPlayer.zip

演示视频见文末

演示视频

回帖(1)

任凭风吹

2022-10-26 14:39:07
太棒了,感谢分享
举报

更多回帖

发帖
×
20
完善资料,
赚取积分