adaptorex
Version:
Connect all your live interactive storytelling devices and software
165 lines (138 loc) • 3.9 kB
JavaScript
/**
*
* simple sound plugin that uses npm package play-sound.
*
* @requires play-sound
* @requires plugin
*
* @module sound/sound
* @copyright Lasse Marburg 2022
* @license MIT
*/
var player = require('play-sound')(opts = {})
const plugin = require('../plugin.js')
const file = require('../../file.js')
var schema = require('./schema.json')
const { ChildProcess } = require('child_process')
class Sound extends plugin.Plugin {
constructor() {
super(schema)
}
setup(config, game) {
super.setup(config, game)
this.addTemplate("play", (payload, action) => {
const title = `sound ${action.name}`
let footer = {text: ""}
let subtitle
const body = []
if(payload.loop) {
if(payload.loop < 0) {
footer["text"] = "Repeat infinite "
} else if(payload.loop != 1) {
footer["text"] = `Repeat ${payload.loop}x `
}
}
for(let track of payload.tracks) {
body.push({text: `${track}`})
}
if(payload.next) {
footer["text"] += `then: ${payload.next}`
footer["next"] = payload.next
}
if(footer.text != "") {
body.push(footer)
}
return {title, subtitle, body}
})
// @todo play a silent testsound to check if player is available
this.connected = true
return this.schema
}
update(settings) {
}
async play(data, session) {
let playback = new Playback(data, session, this.game)
await playback.start()
return playback.cancel.bind(playback)
}
}
class Playback {
/**
* @type {ChildProcess}
* child process executing the current audio playback
*/
process
constructor(properties, session, game) {
Object.assign(this, properties)
this.session = session
this.game = game
}
async start() {
this.donePlayingCallback = this.session.getCallback(this.donePlaying.bind(this))
if(this.loop < 0) {
this.loop = 99999
}
if(!this.loop) {
this.loop = 1
}
this.play_index = 0
await this.playNext(this.tracks[0])
}
playNext(track) {
return new Promise((resolve, reject) => {
if(this.status == "canceled") {
return
}
this.current_audiofile = this.game.getFilePath(track)
if(!file.exists(this.current_audiofile)) {
throw new adaptor.NotFoundError(`Could not find audiofile ${this.current_audiofile}`)
}
this.process = player.play(this.current_audiofile, async err => {
if(this.status == "canceled") {
return
}
this.donePlayingCallback(err)
})
if(!(this.process instanceof ChildProcess)) {
return reject(new Error(`Could not play audio ${this.current_audiofile}. Unable to spawn process with audioplayer: ${player.player}`))
}
this.process.on("error", err => {
return reject(new Error(`Could not play audio ${this.current_audiofile} with audioplayer: ${player.player}\n${err.message}`))
})
this.process.on("spawn", () => {
this.session.log.info(`Play audio ${this.current_audiofile} with ${player.player}`)
return resolve()
})
})
}
async donePlaying(err) {
if(err) {
throw new Error(err)
}
this.session.log("Done playing " + this.tracks[this.play_index])
this.play_index ++
if(this.play_index >= this.tracks.length) {
this.play_index = 0
this.loop --
if(this.loop <= 0) {
this.session.log.info("Played last track in playlist")
if(this.next) {
this.session.next(this.next)
}
return
}
}
try {
await this.playNext(this.tracks[this.play_index])
} catch (error) {
throw error
}
}
cancel() {
this.status = "canceled"
if(this.process) {
this.process.kill()
}
}
}
module.exports.Plugin = Sound