// /
// import { create, id } from "@zos/media";
import { CustomSave, Save } from "../storage/decorator.js";
import { HZEngineCore } from "../index.js";
import { Platform } from "../platform/index.js";
export class Audio {
// static _hmPlayer = create(id.PLAYER);
constructor(public _core: HZEngineCore) {
this._channels["audio"] = new Audio.Channel(this);
}
@CustomSave(
"audio.channels",
function (channels) {
let res = >{};
for (let key in channels) {
res[key] = channels[key].serialize();
}
return res;
},
function (serialized) {
// clear exist channels
for (let key in this._channels) {
this._channels[key].stop();
this._channels[key].onRelease();
}
let res = >{};
for (let key in serialized) {
res[key] = Audio.Channel.deserialize(this, serialized[key]);
}
return res;
}
)
private accessor _channels: Record = {};
get channels() {
return this._channels;
}
createChannel(name: string): Audio.Channel {
// TODO support more audio channels
if (Object.getOwnPropertyNames(this._channels).length > 0)
throw "Can't create more than one audio channel on ZeppOS";
if (this._channels[name]) throw "Channel Already Exist";
return (this._channels[name] = new Audio.Channel(this));
}
}
export namespace Audio {
export class Channel {
// platform specific
// universal
_audioPlayer: Platform.AudioPlayer;
constructor(public _audio: Audio) {
this._audioPlayer = _audio._core.platform.createAudioPlayer();
this._audioPlayer.onPrepared = (success: boolean) => {
this._onPrepared(success);
};
this._audioPlayer.onCompleted = () => {
this._onCompleted();
};
// Audio._hmPlayer.addEventListener(Audio._hmPlayer.event.PREPARE, (res) =>
// this._onPrepared(res)
// );
// Audio._hmPlayer.addEventListener(Audio._hmPlayer.event.COMPLETE, () => {
// this._onCompleted();
// });
}
onRelease() {
this._audioPlayer.release() // TODO
this._audio._core.platform.releaseAudioPlayer(this._audioPlayer);
}
mode: Channel.Mode = Channel.Mode.PlayInOrder;
status: Channel.Status = Channel.Status.Stopped;
_playbackList: PlaybackItem[] = [];
_nowIndex: number | null = null;
currentInfo: AudioInfo | null = null;
push(item: PlaybackItem) {
this._playbackList.push(item);
}
play() {
if (this.status !== Channel.Status.Stopped) return;
this._playNext();
}
pause() {
if (this.status !== Channel.Status.Playing) return;
this._audioPlayer.stop();
this.status = Channel.Status.Stopped;
}
/**
* 停止并清空队列
*/
stop() {
if (this.status !== Channel.Status.Playing) return;
this._audio._core.debug.log("audio channel stop");
this._audioPlayer.stop();
this._playbackList = [];
this.currentInfo = null;
this._nowIndex = null;
this.status = Channel.Status.Stopped;
}
serialize(): Channel.Serialized {
return {
mode: this.mode,
status: this.status,
playbackList: this._playbackList,
nowIndex: this._nowIndex,
};
}
static deserialize(audio: Audio, data: Channel.Serialized): Channel {
let channel = new Channel(audio);
channel.mode = data.mode;
channel.status = data.status;
channel._playbackList = data.playbackList;
channel._nowIndex = data.nowIndex;
if (channel.status === Channel.Status.Playing) channel._playNext();
return channel;
}
_onPrepared(result: boolean) {
if (result) {
let mediaInfo = this._audioPlayer.getMediaInfo();
this.currentInfo = {
// placeholder
artist: mediaInfo.artist ?? "未知",
title: mediaInfo.title ?? "未知",
duration: mediaInfo.duration,
};
console.log("=== prepare succeed ===");
this._audioPlayer.start();
} else {
console.log("=== prepare fail ===");
this.status = Channel.Status.Error;
this._audioPlayer.release();
}
}
_onCompleted() {
// update playback list
if (this.mode === Channel.Mode.PlayInOrder) {
this._playbackList.shift();
if (this._playbackList.length > 0) this._nowIndex = 0;
else this._nowIndex = null;
} else if (this.mode === Channel.Mode.PlayInOrderRepeat) {
if (this._playbackList.length > 0) {
if (this._nowIndex === null) this._nowIndex = 0;
this._nowIndex = (this._nowIndex + 1) % this._playbackList.length;
} else this._nowIndex = null;
}
this._playNext();
}
/**
* Play the next item in the playback list. If there are no items in the
* playback list, set the channel status to Stopped and return.
* @private
*/
_playNext() {
if (this._playbackList.length === 0) {
this._nowIndex = null;
this.status = Channel.Status.Stopped;
return;
}
if (this.mode === Channel.Mode.PlayInOrder) {
if (this._nowIndex === null) this._nowIndex = 0;
// otherwise onCompleted will set _nowIndex to next index
} else if (this.mode === Channel.Mode.PlayInOrderRepeat) {
if (this._nowIndex === null) this._nowIndex = 0;
// otherwise onCompleted will set _nowIndex to next index
}
// Audio._hmPlayer.setSource(Audio._hmPlayer.source.FILE, {
// file: this._playbackList[this._nowIndex!].path,
// });
this._audioPlayer.setSource({
path: this._playbackList[this._nowIndex!].path,
});
this._audioPlayer.prepare();
this.status = Channel.Status.Playing;
}
}
export namespace Channel {
export enum Mode {
PlayInOrder,
PlayInOrderRepeat,
}
export enum Status {
Stopped,
Playing,
Error,
}
export type Serialized = {
mode: Channel.Mode;
status: Channel.Status;
playbackList: PlaybackItem[];
nowIndex: number | null;
};
}
export interface PlaybackItem {
path: string;
}
export interface AudioInfo {
duration: number;
title: string;
artist: string;
}
}