From c3413ba4bd68c5d1f3f8f1bd12814fd4604838e5 Mon Sep 17 00:00:00 2001 From: xnv Date: Mon, 13 Nov 2023 17:57:07 +0900 Subject: [PATCH 01/15] feat: follow pdi-types@1.13.0-beta.0 (AudioAsset#loopOffset) --- .github/workflows/release.yml | 1 + CHANGELOG.md | 4 ++ e2e/tests/audio/HTMLAudio.spec.ts | 12 ++--- e2e/tests/audio/WebAudio.spec.ts | 12 ++--- package-lock.json | 12 ++--- package.json | 7 +-- src/ResourceFactory.ts | 5 +- src/asset/AudioAsset.ts | 5 +- src/plugin/AudioPlugin.ts | 3 +- src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts | 47 +++++++++++-------- src/plugin/HTMLAudioPlugin/HTMLAudioPlugin.ts | 5 +- .../__tests__/HTMLAudioAsset.spec.ts | 9 ++-- .../__tests__/HTMLAudioPlayer.spec.ts | 2 +- .../__tests__/HTMLAudioPlugin.spec.ts | 3 +- .../ProxyAudioPlugin/ProxyAudioAsset.ts | 8 ++-- .../ProxyAudioPlugin/ProxyAudioHandlerSet.ts | 1 + .../ProxyAudioPlugin/ProxyAudioPlugin.ts | 5 +- .../__tests__/ProxyAudioAsset.spec.ts | 17 +++++-- .../__tests__/ProxyAudioPlayer.spec.ts | 10 ++-- .../__tests__/ProxyAudioPlugin.spec.ts | 3 +- src/plugin/WebAudioPlugin/WebAudioPlayer.ts | 21 +++++---- src/plugin/WebAudioPlugin/WebAudioPlugin.ts | 5 +- .../__tests__/WebAudioAsset.spec.ts | 9 ++-- .../__tests__/WebAudioPlayer.spec.ts | 2 +- .../__tests__/WebAudioPlugin.spec.ts | 3 +- 25 files changed, 129 insertions(+), 82 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 64e78ba..a9ff100 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - feat-loopoffset env: NODE_VERSION: 18 diff --git a/CHANGELOG.md b/CHANGELOG.md index d94e62e..3ae3e07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## 2.9.0-beta.0 +* @akashic/pdi-types@1.13.0-beta.0 に追従 + * `AudioAsset#loopOffset` に対応 + ## 2.8.3 * `MouseTouchEventHandler` において `button` の値が一部 `PointerEventHandler` と異なってしまう問題を修正 diff --git a/e2e/tests/audio/HTMLAudio.spec.ts b/e2e/tests/audio/HTMLAudio.spec.ts index e0f25be..fd83fea 100644 --- a/e2e/tests/audio/HTMLAudio.spec.ts +++ b/e2e/tests/audio/HTMLAudio.spec.ts @@ -39,7 +39,7 @@ describe("HTMLAudio", () => { const { HTMLAudioPlugin } = require("@akashic/pdi-browser") as typeof index; const plugin = new HTMLAudioPlugin(); const system = new window.__mock__.MockAudioSystem({id: "voice"}); - const asset = plugin.createAsset("id", audioAssetPath, 100, system, false, {}, 10); + const asset = plugin.createAsset("id", audioAssetPath, 100, system, false, {}, 10, undefined); return new Promise((resolve, reject) => { const loader: AssetLoadHandler = { _onAssetLoad: (asset: AudioAsset) => { @@ -65,7 +65,7 @@ describe("HTMLAudio", () => { const { HTMLAudioPlugin } = require("@akashic/pdi-browser") as typeof index; const plugin = new HTMLAudioPlugin(); const system = new window.__mock__.MockAudioSystem({id: "voice"}); - const asset = plugin.createAsset("id", audioAssetPath + "?" + query, 100, system, false, {}, 0); + const asset = plugin.createAsset("id", audioAssetPath + "?" + query, 100, system, false, {}, 0, undefined); return new Promise((resolve, reject) => { const loader: AssetLoadHandler = { _onAssetLoad: (asset: AudioAsset) => { @@ -87,7 +87,7 @@ describe("HTMLAudio", () => { const { HTMLAudioPlugin } = require("@akashic/pdi-browser") as typeof index; const plugin = new HTMLAudioPlugin(); const system = new window.__mock__.MockAudioSystem({id: "voice"}); - const asset = plugin.createAsset("id", "not_found_audio", 100, system, false, {}, 0); + const asset = plugin.createAsset("id", "not_found_audio", 100, system, false, {}, 0, undefined); return new Promise((resolve, reject) => { const loader: AssetLoadHandler = { _onAssetLoad: (_asset: AudioAsset) => { @@ -112,7 +112,7 @@ describe("HTMLAudio", () => { const plugin = new HTMLAudioPlugin(); plugin.supportedFormats = [format]; const system = new window.__mock__.MockAudioSystem({id: "voice"}); - const asset = plugin.createAsset("id", audioAssetPath, 100, system, false, {}, 0); + const asset = plugin.createAsset("id", audioAssetPath, 100, system, false, {}, 0, undefined); return new Promise((resolve) => { const loader: AssetLoadHandler = { _onAssetLoad: (asset: AudioAsset) => { @@ -139,7 +139,7 @@ describe("HTMLAudio", () => { const plugin = new HTMLAudioPlugin(); plugin.supportedFormats = ["aac", "mp4"]; const system = new window.__mock__.MockAudioSystem({id: "voice"}); - const asset = plugin.createAsset("id", audioAsset2Path + "?" + query, 100, system, false, {}, 0); + const asset = plugin.createAsset("id", audioAsset2Path + "?" + query, 100, system, false, {}, 0, undefined); return new Promise((resolve) => { const loader: AssetLoadHandler = { _onAssetLoad: (asset: AudioAsset) => { @@ -168,7 +168,7 @@ describe("HTMLAudio", () => { const plugin = new HTMLAudioPlugin(); plugin.supportedFormats = ["aac", "mp4"]; const system = new window.__mock__.MockAudioSystem({id: "voice"}); - const asset = plugin.createAsset("id", seAssetPath, 100, system, false, {}, 0); + const asset = plugin.createAsset("id", seAssetPath, 100, system, false, {}, 0, undefined); const player = asset.play(); player.changeVolume(0.1); return new Promise<[AudioAsset, AudioPlayer]>((resolve, reject) => { diff --git a/e2e/tests/audio/WebAudio.spec.ts b/e2e/tests/audio/WebAudio.spec.ts index 85edd72..7f9feec 100644 --- a/e2e/tests/audio/WebAudio.spec.ts +++ b/e2e/tests/audio/WebAudio.spec.ts @@ -39,7 +39,7 @@ describe("WebAudio", () => { const { WebAudioPlugin } = require("@akashic/pdi-browser") as typeof index; const plugin = new WebAudioPlugin(); const system = new window.__mock__.MockAudioSystem({id: "voice"}); - const asset = plugin.createAsset("id", audioAssetPath, 100, system, false, {}, 10); + const asset = plugin.createAsset("id", audioAssetPath, 100, system, false, {}, 10, undefined); return new Promise((resolve, reject) => { const loader: AssetLoadHandler = { _onAssetLoad: (asset: AudioAsset) => { @@ -65,7 +65,7 @@ describe("WebAudio", () => { const { WebAudioPlugin } = require("@akashic/pdi-browser") as typeof index; const plugin = new WebAudioPlugin(); const system = new window.__mock__.MockAudioSystem({id: "voice"}); - const asset = plugin.createAsset("id", audioAssetPath + "?" + query, 100, system, false, {}, 0); + const asset = plugin.createAsset("id", audioAssetPath + "?" + query, 100, system, false, {}, 0, undefined); return new Promise((resolve, reject) => { const loader: AssetLoadHandler = { _onAssetLoad: (asset: AudioAsset) => { @@ -87,7 +87,7 @@ describe("WebAudio", () => { const { WebAudioPlugin } = require("@akashic/pdi-browser") as typeof index; const plugin = new WebAudioPlugin(); const system = new window.__mock__.MockAudioSystem({id: "voice"}); - const asset = plugin.createAsset("id", "not_found_audio", 100, system, false, {}, 0); + const asset = plugin.createAsset("id", "not_found_audio", 100, system, false, {}, 0, undefined); return new Promise((resolve, reject) => { const loader: AssetLoadHandler = { _onAssetLoad: (_asset: AudioAsset) => { @@ -113,7 +113,7 @@ describe("WebAudio", () => { const plugin = new WebAudioPlugin(); plugin.supportedFormats = [format]; const system = new window.__mock__.MockAudioSystem({id: "voice"}); - const asset = plugin.createAsset("id", audioAssetPath, 100, system, false, {}, 0); + const asset = plugin.createAsset("id", audioAssetPath, 100, system, false, {}, 0, undefined); return new Promise((resolve) => { const loader: AssetLoadHandler = { _onAssetLoad: (asset: AudioAsset) => { @@ -140,7 +140,7 @@ describe("WebAudio", () => { const plugin = new WebAudioPlugin(); plugin.supportedFormats = ["aac"]; const system = new window.__mock__.MockAudioSystem({id: "voice"}); - const asset = plugin.createAsset("id", audioAsset2Path + "?" + query, 100, system, false, {}, 0); + const asset = plugin.createAsset("id", audioAsset2Path + "?" + query, 100, system, false, {}, 0, undefined); return new Promise((resolve) => { const loader: AssetLoadHandler = { _onAssetLoad: (asset: AudioAsset) => { @@ -168,7 +168,7 @@ describe("WebAudio", () => { const { WebAudioPlugin } = require("@akashic/pdi-browser") as typeof index; const plugin = new WebAudioPlugin(); const system = new window.__mock__.MockAudioSystem({id: "voice"}); - const asset = plugin.createAsset("id", seAssetPath, 100, system, false, {}, 0); + const asset = plugin.createAsset("id", seAssetPath, 100, system, false, {}, 0, undefined); const player = asset.play(); player.changeVolume(0.1); return new Promise<[AudioAsset, AudioPlayer]>((resolve, reject) => { diff --git a/package-lock.json b/package-lock.json index fb8af64..da32d8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@akashic/pdi-browser", - "version": "2.8.3", + "version": "2.9.0-beta.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@akashic/pdi-browser", - "version": "2.8.3", + "version": "2.9.0-beta.0", "license": "MIT", "dependencies": { "@akashic/trigger": "^2.0.1" @@ -14,7 +14,7 @@ "devDependencies": { "@akashic/amflow": "^3.3.0", "@akashic/eslint-config": "^1.1.1", - "@akashic/pdi-types": "^1.12.0", + "@akashic/pdi-types": "^1.13.0-beta.0", "@akashic/playlog": "^3.3.0", "@types/jest": "^29.2.0", "@types/node": "^18.0.0", @@ -91,9 +91,9 @@ } }, "node_modules/@akashic/pdi-types": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@akashic/pdi-types/-/pdi-types-1.12.0.tgz", - "integrity": "sha512-kgjUvhdDBFX0oDRy2imdST49T13cSBnRKy09sryWOxMicmq6JvVI3hMuqyyQoc18csr46N8DonkHFSE49skCXA==", + "version": "1.13.0-beta.0", + "resolved": "https://registry.npmjs.org/@akashic/pdi-types/-/pdi-types-1.13.0-beta.0.tgz", + "integrity": "sha512-0sp5sBye5/EcYlAhWfGxV2qkFHPheluI1rYionzS9JojNi24j+aBBrr7oJoynyV5Lf0rSIPcCIlATv4qC/CeVw==", "dev": true, "dependencies": { "@akashic/amflow": "~3.3.0", diff --git a/package.json b/package.json index 0632ac6..afe5595 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@akashic/pdi-browser", - "version": "2.8.3", + "version": "2.9.0-beta.0", "description": "An akashic-pdi implementation for Web browsers", "main": "index.js", "typings": "lib/full/index.d.ts", @@ -43,7 +43,7 @@ "devDependencies": { "@akashic/amflow": "^3.3.0", "@akashic/eslint-config": "^1.1.1", - "@akashic/pdi-types": "^1.12.0", + "@akashic/pdi-types": "^1.13.0-beta.0", "@akashic/playlog": "^3.3.0", "@types/jest": "^29.2.0", "@types/node": "^18.0.0", @@ -74,6 +74,7 @@ "@akashic/trigger": "^2.0.1" }, "publishConfig": { - "@akashic:registry": "https://registry.npmjs.org/" + "@akashic:registry": "https://registry.npmjs.org/", + "tag": "next" } } diff --git a/src/ResourceFactory.ts b/src/ResourceFactory.ts index a5e0eec..47f9e4d 100644 --- a/src/ResourceFactory.ts +++ b/src/ResourceFactory.ts @@ -41,13 +41,14 @@ export class ResourceFactory implements pdi.ResourceFactory { system: pdi.AudioSystem, loop: boolean, hint: pdi.AudioAssetHint, - offset: number + offset: number, + loopOffset: number ): AudioAsset { const activePlugin = this._audioPluginManager.getActivePlugin(); if (!activePlugin) { throw new Error("ResourceFactory#createAudioAsset(): could not initialize ActivePlugin"); } - const audioAsset = activePlugin.createAsset(id, assetPath, duration, system, loop, hint, offset); + const audioAsset = activePlugin.createAsset(id, assetPath, duration, system, loop, hint, offset, loopOffset); this._audioManager.registerAudioAsset(audioAsset); audioAsset.onDestroyed.addOnce(this._onAudioAssetDestroyed, this); return audioAsset; diff --git a/src/asset/AudioAsset.ts b/src/asset/AudioAsset.ts index ad61cde..f140b6b 100644 --- a/src/asset/AudioAsset.ts +++ b/src/asset/AudioAsset.ts @@ -8,6 +8,7 @@ export abstract class AudioAsset extends Asset implements pdi.AudioAsset { loop: boolean; hint: pdi.AudioAssetHint; offset: number; + loopOffset: number | undefined; _system: pdi.AudioSystem; _lastPlayedPlayer: pdi.AudioPlayer | undefined; @@ -18,7 +19,8 @@ export abstract class AudioAsset extends Asset implements pdi.AudioAsset { system: pdi.AudioSystem, loop: boolean, hint: pdi.AudioAssetHint, - offset: number + offset: number, + loopOffset: number | undefined, ) { super(id, path); this.duration = duration; @@ -26,6 +28,7 @@ export abstract class AudioAsset extends Asset implements pdi.AudioAsset { this.hint = hint; this._system = system; this.offset = offset; + this.loopOffset = loopOffset; this.path = this._modifyPath(this.path); } diff --git a/src/plugin/AudioPlugin.ts b/src/plugin/AudioPlugin.ts index 7339186..850645b 100644 --- a/src/plugin/AudioPlugin.ts +++ b/src/plugin/AudioPlugin.ts @@ -19,7 +19,8 @@ export interface AudioPlugin { system: pdi.AudioSystem, loop: boolean, hint: pdi.AudioAssetHint, - offset: number + offset: number, + loopOffset: number, ) => AudioAsset; createPlayer: (system: pdi.AudioSystem, manager: AudioManager) => AudioPlayer; diff --git a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts index 6c42a80..15a9d98 100644 --- a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts +++ b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts @@ -32,34 +32,41 @@ export class HTMLAudioPlayer extends AudioPlayer { const audio = asset.cloneElement(); if (audio) { - if (asset.offset === undefined) { - // offsetが指定されていない場合、durationを無視して全体再生する + // NOTE: 後方互換のため、offset の指定がない場合は duration を無視 (終端まで再生) + const duration = (asset.duration != null && asset.offset != null) ? asset.duration / 1000 : null; + const offset = (asset.offset ?? 0) / 1000; + const loopStart = (asset.loop && asset.loopOffset != null) ? asset.loopOffset / 1000 : offset; + const end = (duration != null) ? offset + duration : null; + + audio.currentTime = offset; + if (loopStart === 0 && end == null) { audio.loop = asset.loop; + audio.addEventListener("ended", this._endedEventHandler); } else { - const offsetSec = (asset.offset ?? 0) / 1000; - const durationEndSec = asset.duration / 1000 + offsetSec; - audio.currentTime = offsetSec; - audio.ontimeupdate = () => { - if (durationEndSec <= audio.currentTime) { - if (asset.loop) { - audio.currentTime = offsetSec; - } else { - audio.pause(); - } - } - }; - audio.onended = () => { - if (asset.loop) { - audio.currentTime = offsetSec; + if (!asset.loop) { + audio.addEventListener("ended", this._endedEventHandler); + } else { + audio.addEventListener("ended", () => { + audio.currentTime = loopStart; audio.play(); - } - }; + }); + } + if (end != null) { + audio.addEventListener("timeupdate", () => { + if (end <= audio.currentTime) { + if (asset.loop) { + audio.currentTime = loopStart; + } else { + audio.pause(); + } + } + }); + } } setupChromeMEIWorkaround(audio); audio.volume = this._calculateVolume(); audio.play().catch((_err) => { /* user interactの前にplay()を呼ぶとエラーになる。これはHTMLAudioAutoplayHelperで吸収する */}); - audio.addEventListener("ended", this._endedEventHandler, false); audio.addEventListener("play", this._onPlayEventHandler, false); this._isWaitingPlayEvent = true; this._audioInstance = audio; diff --git a/src/plugin/HTMLAudioPlugin/HTMLAudioPlugin.ts b/src/plugin/HTMLAudioPlugin/HTMLAudioPlugin.ts index 6693bdb..5d40d76 100644 --- a/src/plugin/HTMLAudioPlugin/HTMLAudioPlugin.ts +++ b/src/plugin/HTMLAudioPlugin/HTMLAudioPlugin.ts @@ -46,9 +46,10 @@ export class HTMLAudioPlugin implements AudioPlugin { system: pdi.AudioSystem, loop: boolean, hint: pdi.AudioAssetHint, - offset: number + offset: number, + loopOffset: number | undefined ): AudioAsset { - return new HTMLAudioAsset(id, path, duration, system, loop, hint, offset); + return new HTMLAudioAsset(id, path, duration, system, loop, hint, offset, loopOffset); } createPlayer(system: pdi.AudioSystem, manager: AudioManager): AudioPlayer { diff --git a/src/plugin/HTMLAudioPlugin/__tests__/HTMLAudioAsset.spec.ts b/src/plugin/HTMLAudioPlugin/__tests__/HTMLAudioAsset.spec.ts index 560ed9c..5e65f9c 100644 --- a/src/plugin/HTMLAudioPlugin/__tests__/HTMLAudioAsset.spec.ts +++ b/src/plugin/HTMLAudioPlugin/__tests__/HTMLAudioAsset.spec.ts @@ -8,10 +8,11 @@ describe("HTMLAudioAsset", () => { it("can instantiate", () => { const system = new MockAudioSystem({ id: "audio-system" }); - const asset = new HTMLAudioAsset("audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 10); + const asset = new HTMLAudioAsset("audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 10, undefined); expect(asset.id).toBe("audio-asset"); expect(asset.path).toBe("/path/to/audio.ogg"); expect(asset.offset).toBe(10); + expect(asset.loopOffset).toBeUndefined(); expect(asset.duration).toBe(100); expect(asset.hint).toEqual({ streaming: false }); expect(asset.loop).toBe(false); @@ -27,7 +28,8 @@ describe("HTMLAudioAsset", () => { system, false, { streaming: false, extensions: [".m4a", ".aac"] }, - 10 + 10, + undefined ); expect(asset.id).toBe("audio-asset"); expect(asset.path).toBe("/path/to/audio.m4a"); @@ -42,7 +44,8 @@ describe("HTMLAudioAsset", () => { system, false, { streaming: false, extensions: [".mp4", ".aac"] }, - 10 + 10, + undefined ); expect(asset.id).toBe("audio-asset"); expect(asset.path).toBe("/path/to/audio.aac"); diff --git a/src/plugin/HTMLAudioPlugin/__tests__/HTMLAudioPlayer.spec.ts b/src/plugin/HTMLAudioPlugin/__tests__/HTMLAudioPlayer.spec.ts index 2509831..1ae4039 100644 --- a/src/plugin/HTMLAudioPlugin/__tests__/HTMLAudioPlayer.spec.ts +++ b/src/plugin/HTMLAudioPlugin/__tests__/HTMLAudioPlayer.spec.ts @@ -22,7 +22,7 @@ describe("HTMLAudioPlayer", () => { const system = new MockAudioSystem({ id: "audio-system" }); const manager = new AudioManager(); const player = new HTMLAudioPlayer(system, manager); - const asset = new HTMLAudioAsset("audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 0); + const asset = new HTMLAudioAsset("audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 0, undefined); expect(player.currentAudio).toBeUndefined(); diff --git a/src/plugin/HTMLAudioPlugin/__tests__/HTMLAudioPlugin.spec.ts b/src/plugin/HTMLAudioPlugin/__tests__/HTMLAudioPlugin.spec.ts index 3ff65fb..6d335cf 100644 --- a/src/plugin/HTMLAudioPlugin/__tests__/HTMLAudioPlugin.spec.ts +++ b/src/plugin/HTMLAudioPlugin/__tests__/HTMLAudioPlugin.spec.ts @@ -13,11 +13,12 @@ describe("HTMLAudioPlugin", () => { const plugin = new HTMLAudioPlugin(); plugin.supportedFormats = ["ogg", "aac"]; const system = new MockAudioSystem({ id: "audio-system" }); - const asset = plugin.createAsset("audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 10); + const asset = plugin.createAsset("audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 10, 20); expect(asset.id).toBe("audio-asset"); expect(asset.path).toBe("/path/to/audio.ogg"); expect(asset.offset).toBe(10); + expect(asset.loopOffset).toBe(20); expect(asset.duration).toBe(100); expect(asset.hint).toEqual({ streaming: false }); expect(asset.loop).toBe(false); diff --git a/src/plugin/ProxyAudioPlugin/ProxyAudioAsset.ts b/src/plugin/ProxyAudioPlugin/ProxyAudioAsset.ts index 2479883..617383c 100644 --- a/src/plugin/ProxyAudioPlugin/ProxyAudioAsset.ts +++ b/src/plugin/ProxyAudioPlugin/ProxyAudioAsset.ts @@ -14,9 +14,10 @@ export class ProxyAudioAsset extends AudioAsset { system: pdi.AudioSystem, loop: boolean, hint: pdi.AudioAssetHint, - offset: number + offset: number, + loopOffset: number | undefined, ) { - super(id, assetPath, duration, system, loop, hint, offset); + super(id, assetPath, duration, system, loop, hint, offset, loopOffset); this._handlerSet = handlerSet; } @@ -32,7 +33,8 @@ export class ProxyAudioAsset extends AudioAsset { duration: this.duration, loop: this.loop, hint: this.hint, - offset: this.offset + offset: this.offset, + loopOffset: this.loopOffset }, (err?: any) => { if (err) { loader._onAssetError(this, ExceptionFactory.createAssetLoadError(err)); diff --git a/src/plugin/ProxyAudioPlugin/ProxyAudioHandlerSet.ts b/src/plugin/ProxyAudioPlugin/ProxyAudioHandlerSet.ts index 5edf14a..8039c4a 100644 --- a/src/plugin/ProxyAudioPlugin/ProxyAudioHandlerSet.ts +++ b/src/plugin/ProxyAudioPlugin/ProxyAudioHandlerSet.ts @@ -9,6 +9,7 @@ export interface LoadAudioAssetParameterObject { loop: boolean; hint: AudioAssetHintParameterObject; offset: number; + loopOffset: number | undefined; } export interface CreateAudioPlayerParameterObject { diff --git a/src/plugin/ProxyAudioPlugin/ProxyAudioPlugin.ts b/src/plugin/ProxyAudioPlugin/ProxyAudioPlugin.ts index bd1a9b8..1eb07a6 100644 --- a/src/plugin/ProxyAudioPlugin/ProxyAudioPlugin.ts +++ b/src/plugin/ProxyAudioPlugin/ProxyAudioPlugin.ts @@ -27,9 +27,10 @@ export class ProxyAudioPlugin implements AudioPlugin { system: pdi.AudioSystem, loop: boolean, hint: pdi.AudioAssetHint, - offset: number + offset: number, + loopOffset: number | undefined ): AudioAsset { - return new ProxyAudioAsset(this._handlerSet, id, assetPath, duration, system, loop, hint, offset); + return new ProxyAudioAsset(this._handlerSet, id, assetPath, duration, system, loop, hint, offset, loopOffset); } createPlayer(system: pdi.AudioSystem, manager: AudioManager): AudioPlayer { diff --git a/src/plugin/ProxyAudioPlugin/__tests__/ProxyAudioAsset.spec.ts b/src/plugin/ProxyAudioPlugin/__tests__/ProxyAudioAsset.spec.ts index 5f9b9f3..58243a1 100644 --- a/src/plugin/ProxyAudioPlugin/__tests__/ProxyAudioAsset.spec.ts +++ b/src/plugin/ProxyAudioPlugin/__tests__/ProxyAudioAsset.spec.ts @@ -6,11 +6,22 @@ describe("ProxyAudioAsset", () => { it("can instantiate", () => { const system = new MockAudioSystem({ id: "audio-system" }); const handlerSet = new MockProxyAudioHandlerSet(); - const asset = new ProxyAudioAsset(handlerSet, "audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 10); + const asset = new ProxyAudioAsset( + handlerSet, + "audio-asset", + "/path/to/audio", + 100, + system, + false, + { streaming: false }, + 10, + undefined + ); expect(asset.id).toBe("audio-asset"); expect(asset.path).toBe("/path/to/audio"); expect(asset.offset).toBe(10); + expect(asset.loopOffset).toBeUndefined(); expect(asset.duration).toBe(100); expect(asset.hint).toEqual({ streaming: false }); expect(asset.loop).toBe(false); @@ -20,7 +31,7 @@ describe("ProxyAudioAsset", () => { it("can load", done => { const system = new MockAudioSystem({ id: "audio-system" }); const handlerSet = new MockProxyAudioHandlerSet(); - const asset = new ProxyAudioAsset(handlerSet, "audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 10); + const asset = new ProxyAudioAsset(handlerSet, "audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 10, 20); asset._load({ _onAssetLoad: (a) => { @@ -37,7 +48,7 @@ describe("ProxyAudioAsset", () => { it("can unload", done => { const system = new MockAudioSystem({ id: "audio-system" }); const handlerSet = new MockProxyAudioHandlerSet(); - const asset = new ProxyAudioAsset(handlerSet, "audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 10); + const asset = new ProxyAudioAsset(handlerSet, "audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 10, 20); asset._load({ _onAssetLoad: (a) => { diff --git a/src/plugin/ProxyAudioPlugin/__tests__/ProxyAudioPlayer.spec.ts b/src/plugin/ProxyAudioPlugin/__tests__/ProxyAudioPlayer.spec.ts index 7190df1..f3957e5 100644 --- a/src/plugin/ProxyAudioPlugin/__tests__/ProxyAudioPlayer.spec.ts +++ b/src/plugin/ProxyAudioPlugin/__tests__/ProxyAudioPlayer.spec.ts @@ -25,7 +25,7 @@ describe("ProxyAudioPlayer", () => { const manager = new AudioManager(); const handlerSet = new MockProxyAudioHandlerSet(); const player = new ProxyAudioPlayer(handlerSet, system, manager); - const asset = new ProxyAudioAsset(handlerSet, "audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 0); + const asset = new ProxyAudioAsset(handlerSet, "audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 0, 20); expect(player.currentAudio).toBeUndefined(); expect(handlerSet.playAudioPlayer).not.toBeCalled(); @@ -44,7 +44,7 @@ describe("ProxyAudioPlayer", () => { const manager = new AudioManager(); const handlerSet = new MockProxyAudioHandlerSet(); const player = new ProxyAudioPlayer(handlerSet, system, manager); - const asset = new ProxyAudioAsset(handlerSet, "audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 0); + const asset = new ProxyAudioAsset(handlerSet, "audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 0, 20); player.play(asset); player.play(asset); @@ -57,7 +57,7 @@ describe("ProxyAudioPlayer", () => { const manager = new AudioManager(); const handlerSet = new MockProxyAudioHandlerSet(); const player = new ProxyAudioPlayer(handlerSet, system, manager); - const asset = new ProxyAudioAsset(handlerSet, "audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 0); + const asset = new ProxyAudioAsset(handlerSet, "audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 0, 20); expect(handlerSet.createAudioPlayer).not.toBeCalled(); @@ -76,7 +76,7 @@ describe("ProxyAudioPlayer", () => { const manager = new AudioManager(); const handlerSet = new MockProxyAudioHandlerSet(); const player = new ProxyAudioPlayer(handlerSet, system, manager); - const asset = new ProxyAudioAsset(handlerSet, "audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 0); + const asset = new ProxyAudioAsset(handlerSet, "audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 0, 20); expect(handlerSet.destroyAudioPlayer).not.toBeCalled(); @@ -90,7 +90,7 @@ describe("ProxyAudioPlayer", () => { const manager = new AudioManager(); const handlerSet = new MockProxyAudioHandlerSet(); const player = new ProxyAudioPlayer(handlerSet, system, manager); - const asset = new ProxyAudioAsset(handlerSet, "audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 0); + const asset = new ProxyAudioAsset(handlerSet, "audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 0, 20); expect(handlerSet.changeAudioVolume).not.toBeCalled(); diff --git a/src/plugin/ProxyAudioPlugin/__tests__/ProxyAudioPlugin.spec.ts b/src/plugin/ProxyAudioPlugin/__tests__/ProxyAudioPlugin.spec.ts index d103136..8759532 100644 --- a/src/plugin/ProxyAudioPlugin/__tests__/ProxyAudioPlugin.spec.ts +++ b/src/plugin/ProxyAudioPlugin/__tests__/ProxyAudioPlugin.spec.ts @@ -13,11 +13,12 @@ describe("ProxyAudioPlugin", () => { const handlerSet = new MockProxyAudioHandlerSet(); const plugin = new ProxyAudioPlugin(handlerSet); const system = new MockAudioSystem({ id: "audio-system" }); - const asset = plugin.createAsset("audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 10); + const asset = plugin.createAsset("audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 10, 20); expect(asset.id).toBe("audio-asset"); expect(asset.path).toBe("/path/to/audio"); expect(asset.offset).toBe(10); + expect(asset.loopOffset).toBe(20); expect(asset.duration).toBe(100); expect(asset.hint).toEqual({ streaming: false }); expect(asset.loop).toBe(false); diff --git a/src/plugin/WebAudioPlugin/WebAudioPlayer.ts b/src/plugin/WebAudioPlugin/WebAudioPlayer.ts index d18de98..171bdc5 100644 --- a/src/plugin/WebAudioPlugin/WebAudioPlayer.ts +++ b/src/plugin/WebAudioPlugin/WebAudioPlayer.ts @@ -38,6 +38,7 @@ export class WebAudioPlayer extends AudioPlayer { if (this.currentAudio) { this.stop(); } + if (asset.data) { const bufferNode = helper.createBufferNode(this._audioContext); bufferNode.buffer = asset.data; @@ -47,18 +48,20 @@ export class WebAudioPlayer extends AudioPlayer { // Chromeだとevent listerで指定した場合に動かないことがある // https://github.com/mozilla-appmaker/appmaker/issues/1984 this._sourceNode.onended = this._endedEventHandler; - // loop時にoffsetを指定すると正しく動作しないことがあるため、暫定対応としてloopが真の場合はoffsetを指定しない + + bufferNode.loop = asset.loop; + + const offset = (asset.offset ?? 0) / 1000; + const duration = (asset.duration != null) ? asset.duration / 1000 : undefined; if (asset.loop) { - bufferNode.loop = asset.loop; - this._sourceNode.start(0); + bufferNode.loopStart = asset.loopOffset ? (asset.loopOffset / 1000) : offset; + if (duration != null) + bufferNode.loopEnd = offset + duration; + this._sourceNode.start(0, offset); } else { - const offset = (asset.offset ?? 0) / 1000; - if (asset.duration > 0) { - this._sourceNode.start(0, offset, asset.duration / 1000); - } else { - this._sourceNode.start(0, offset); - } + this._sourceNode.start(0, offset, duration); } + } else { // 再生できるオーディオがない場合。duration後に停止処理だけ行う(処理のみ進め音は鳴らさない) this._dummyDurationWaitTimer = setTimeout(this._endedEventHandler, asset.duration); diff --git a/src/plugin/WebAudioPlugin/WebAudioPlugin.ts b/src/plugin/WebAudioPlugin/WebAudioPlugin.ts index b3e8953..5a54425 100644 --- a/src/plugin/WebAudioPlugin/WebAudioPlugin.ts +++ b/src/plugin/WebAudioPlugin/WebAudioPlugin.ts @@ -46,9 +46,10 @@ export class WebAudioPlugin implements AudioPlugin { system: pdi.AudioSystem, loop: boolean, hint: pdi.AudioAssetHint, - offset: number + offset: number, + loopOffset: number | undefined, ): AudioAsset { - return new WebAudioAsset(id, assetPath, duration, system, loop, hint, offset); + return new WebAudioAsset(id, assetPath, duration, system, loop, hint, offset, loopOffset); } createPlayer(system: pdi.AudioSystem, manager: AudioManager): AudioPlayer { diff --git a/src/plugin/WebAudioPlugin/__tests__/WebAudioAsset.spec.ts b/src/plugin/WebAudioPlugin/__tests__/WebAudioAsset.spec.ts index 0e67141..5586edb 100644 --- a/src/plugin/WebAudioPlugin/__tests__/WebAudioAsset.spec.ts +++ b/src/plugin/WebAudioPlugin/__tests__/WebAudioAsset.spec.ts @@ -10,10 +10,11 @@ describe("HTMLAudioAsset", () => { it("can instantiate", () => { const system = new MockAudioSystem({ id: "audio-system" }); - const asset = new WebAudioAsset("audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 10); + const asset = new WebAudioAsset("audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 10, 20); expect(asset.id).toBe("audio-asset"); expect(asset.path).toBe("/path/to/audio.ogg"); expect(asset.offset).toBe(10); + expect(asset.loopOffset).toBe(20); expect(asset.duration).toBe(100); expect(asset.hint).toEqual({ streaming: false }); expect(asset.loop).toBe(false); @@ -29,7 +30,8 @@ describe("HTMLAudioAsset", () => { system, false, { streaming: false, extensions: [".m4a", ".aac"] }, - 10 + 10, + undefined ); expect(asset.id).toBe("audio-asset"); expect(asset.path).toBe("/path/to/audio.m4a"); @@ -44,7 +46,8 @@ describe("HTMLAudioAsset", () => { system, false, { streaming: false, extensions: [".mp4", ".aac"] }, - 10 + 10, + undefined ); expect(asset.id).toBe("audio-asset"); expect(asset.path).toBe("/path/to/audio.aac"); diff --git a/src/plugin/WebAudioPlugin/__tests__/WebAudioPlayer.spec.ts b/src/plugin/WebAudioPlugin/__tests__/WebAudioPlayer.spec.ts index 5d1cc66..8b45da8 100644 --- a/src/plugin/WebAudioPlugin/__tests__/WebAudioPlayer.spec.ts +++ b/src/plugin/WebAudioPlugin/__tests__/WebAudioPlayer.spec.ts @@ -24,7 +24,7 @@ describe("WebAudioPlayer", () => { const system = new MockAudioSystem({ id: "audio-system" }); const manager = new AudioManager(); const player = new WebAudioPlayer(system, manager); - const asset = new WebAudioAsset("audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 0); + const asset = new WebAudioAsset("audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 0, undefined); expect(player.currentAudio).toBeUndefined(); diff --git a/src/plugin/WebAudioPlugin/__tests__/WebAudioPlugin.spec.ts b/src/plugin/WebAudioPlugin/__tests__/WebAudioPlugin.spec.ts index b36b6a1..a27dcb6 100644 --- a/src/plugin/WebAudioPlugin/__tests__/WebAudioPlugin.spec.ts +++ b/src/plugin/WebAudioPlugin/__tests__/WebAudioPlugin.spec.ts @@ -15,11 +15,12 @@ describe("WebAudioPlugin", () => { const plugin = new WebAudioPlugin(); plugin.supportedFormats = ["ogg", "aac"]; const system = new MockAudioSystem({ id: "audio-system" }); - const asset = plugin.createAsset("audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 10); + const asset = plugin.createAsset("audio-asset", "/path/to/audio", 100, system, false, { streaming: false }, 10, 20); expect(asset.id).toBe("audio-asset"); expect(asset.path).toBe("/path/to/audio.ogg"); expect(asset.offset).toBe(10); + expect(asset.loopOffset).toBe(20); expect(asset.duration).toBe(100); expect(asset.hint).toEqual({ streaming: false }); expect(asset.loop).toBe(false); From 718ce9c97f8bba007259fe3b039ff71e3acb6b89 Mon Sep 17 00:00:00 2001 From: kamakiri01 Date: Mon, 11 Dec 2023 21:24:57 +0900 Subject: [PATCH 02/15] loop check with setTimeout --- src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts index 15a9d98..9325494 100644 --- a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts +++ b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts @@ -52,15 +52,27 @@ export class HTMLAudioPlayer extends AudioPlayer { }); } if (end != null) { + const onEnded: () => void = () => { + if (asset.loop) { + audio.currentTime = loopStart; + } else { + audio.pause(); + } + }; + const setTimer = function(): void { + setTimeout(() => { + onEnded(); + setTimer(); + }, (end - loopStart) * 1000); + }; + // timeupdate イベント通知の精度が低いため、 setTimeout と併用する + // addEventListener は裏タブ時にも動作させるため残す audio.addEventListener("timeupdate", () => { if (end <= audio.currentTime) { - if (asset.loop) { - audio.currentTime = loopStart; - } else { - audio.pause(); - } + onEnded(); } }); + setTimer(); } } From 2b4915a7fd92aaaee75b84dec312831e990f17eb Mon Sep 17 00:00:00 2001 From: kamakiri01 Date: Mon, 11 Dec 2023 21:38:53 +0900 Subject: [PATCH 03/15] Revert "loop check with setTimeout" This reverts commit 718ce9c97f8bba007259fe3b039ff71e3acb6b89. --- src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts index 9325494..15a9d98 100644 --- a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts +++ b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts @@ -52,27 +52,15 @@ export class HTMLAudioPlayer extends AudioPlayer { }); } if (end != null) { - const onEnded: () => void = () => { - if (asset.loop) { - audio.currentTime = loopStart; - } else { - audio.pause(); - } - }; - const setTimer = function(): void { - setTimeout(() => { - onEnded(); - setTimer(); - }, (end - loopStart) * 1000); - }; - // timeupdate イベント通知の精度が低いため、 setTimeout と併用する - // addEventListener は裏タブ時にも動作させるため残す audio.addEventListener("timeupdate", () => { if (end <= audio.currentTime) { - onEnded(); + if (asset.loop) { + audio.currentTime = loopStart; + } else { + audio.pause(); + } } }); - setTimer(); } } From 9c184102ac2c58bda024d994d95e195395a3d97f Mon Sep 17 00:00:00 2001 From: kamakiri01 Date: Mon, 11 Dec 2023 21:40:02 +0900 Subject: [PATCH 04/15] loop check with setTimeout --- src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts index 15a9d98..9325494 100644 --- a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts +++ b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts @@ -52,15 +52,27 @@ export class HTMLAudioPlayer extends AudioPlayer { }); } if (end != null) { + const onEnded: () => void = () => { + if (asset.loop) { + audio.currentTime = loopStart; + } else { + audio.pause(); + } + }; + const setTimer = function(): void { + setTimeout(() => { + onEnded(); + setTimer(); + }, (end - loopStart) * 1000); + }; + // timeupdate イベント通知の精度が低いため、 setTimeout と併用する + // addEventListener は裏タブ時にも動作させるため残す audio.addEventListener("timeupdate", () => { if (end <= audio.currentTime) { - if (asset.loop) { - audio.currentTime = loopStart; - } else { - audio.pause(); - } + onEnded(); } }); + setTimer(); } } From 070047124c816e005ddbcd7241e279ba1ad0b5f1 Mon Sep 17 00:00:00 2001 From: kamakiri01 Date: Wed, 13 Dec 2023 11:42:15 +0900 Subject: [PATCH 05/15] fix --- src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts index 9325494..eb04ef2 100644 --- a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts +++ b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts @@ -52,7 +52,7 @@ export class HTMLAudioPlayer extends AudioPlayer { }); } if (end != null) { - const onEnded: () => void = () => { + const onEnded= function(): void { if (asset.loop) { audio.currentTime = loopStart; } else { From 8dc013289909ea47141e81b079748a6ad394060d Mon Sep 17 00:00:00 2001 From: kamakiri01 Date: Fri, 15 Dec 2023 13:37:41 +0900 Subject: [PATCH 06/15] handle loop id --- src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts index eb04ef2..6fdc6f4 100644 --- a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts +++ b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts @@ -12,6 +12,7 @@ export class HTMLAudioPlayer extends AudioPlayer { private _isStopRequested: boolean = false; private _onPlayEventHandler: () => void; private _dummyDurationWaitTimer: any; + private _loopTimeoutId: any; constructor(system: pdi.AudioSystem, manager: AudioManager) { super(system); @@ -52,18 +53,25 @@ export class HTMLAudioPlayer extends AudioPlayer { }); } if (end != null) { - const onEnded= function(): void { + const defaultDelayTime = (end - loopStart) * 1000; + const onEnded: () => void = () => { if (asset.loop) { audio.currentTime = loopStart; } else { audio.pause(); } }; - const setTimer = function(): void { - setTimeout(() => { - onEnded(); - setTimer(); - }, (end - loopStart) * 1000); + const setTimer = (delay: number): void => { + console.log("setTimer", delay); + this. _loopTimeoutId = setTimeout(() => { + console.log("t", end, audio.currentTime); + if (end <= audio.currentTime) { + onEnded(); + if (asset.loop) setTimer(defaultDelayTime); + } else { + setTimer((end - audio.currentTime) * 1000); + } + }, delay); }; // timeupdate イベント通知の精度が低いため、 setTimeout と併用する // addEventListener は裏タブ時にも動作させるため残す @@ -72,7 +80,7 @@ export class HTMLAudioPlayer extends AudioPlayer { onEnded(); } }); - setTimer(); + setTimer(defaultDelayTime); } } @@ -140,6 +148,10 @@ export class HTMLAudioPlayer extends AudioPlayer { clearTimeout(this._dummyDurationWaitTimer); this._dummyDurationWaitTimer = null; } + if (this._loopTimeoutId != null) { + clearTimeout(this._loopTimeoutId); + this._loopTimeoutId = null; + } } // audio.play() は非同期なので、 play が開始される前に stop を呼ばれた場合はこのハンドラ到達時に停止する From a179dbb9c3957654b70f05d7b67af7f76e689965 Mon Sep 17 00:00:00 2001 From: kamakiri01 Date: Mon, 25 Dec 2023 23:21:42 +0900 Subject: [PATCH 07/15] loop audio at expected time from timeupdate diff --- src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts index 6fdc6f4..d44e327 100644 --- a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts +++ b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts @@ -53,34 +53,32 @@ export class HTMLAudioPlayer extends AudioPlayer { }); } if (end != null) { - const defaultDelayTime = (end - loopStart) * 1000; - const onEnded: () => void = () => { + let previousCurrentTime = 0; + let timerId: any; + const clearTimer = (): void => { + if (!timerId) clearTimeout(timerId); + timerId = null; + }; + const onEnded = function (): void { + clearTimer(); if (asset.loop) { audio.currentTime = loopStart; } else { audio.pause(); } }; - const setTimer = (delay: number): void => { - console.log("setTimer", delay); - this. _loopTimeoutId = setTimeout(() => { - console.log("t", end, audio.currentTime); - if (end <= audio.currentTime) { - onEnded(); - if (asset.loop) setTimer(defaultDelayTime); - } else { - setTimer((end - audio.currentTime) * 1000); - } - }, delay); - }; - // timeupdate イベント通知の精度が低いため、 setTimeout と併用する - // addEventListener は裏タブ時にも動作させるため残す audio.addEventListener("timeupdate", () => { + const diff = Math.max(0, audio.currentTime - previousCurrentTime); + previousCurrentTime = audio.currentTime; if (end <= audio.currentTime) { onEnded(); + } else if (end <= audio.currentTime + diff) { // 次の timeupdate イベントまでに end を超えることが確定していれば、見越し時間で停止処理を行う + clearTimer(); + timerId = setTimeout(() => { + onEnded(); + }, (end - audio.currentTime) * 1000); } }); - setTimer(defaultDelayTime); } } From 292e1df9be44232da747016d84db456bea60b431 Mon Sep 17 00:00:00 2001 From: kamakiri01 Date: Tue, 26 Dec 2023 11:52:43 +0900 Subject: [PATCH 08/15] use _loopTimeoutId --- src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts index d44e327..406e1d4 100644 --- a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts +++ b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts @@ -54,13 +54,8 @@ export class HTMLAudioPlayer extends AudioPlayer { } if (end != null) { let previousCurrentTime = 0; - let timerId: any; - const clearTimer = (): void => { - if (!timerId) clearTimeout(timerId); - timerId = null; - }; - const onEnded = function (): void { - clearTimer(); + const onEnded = (): void => { + this._clearTimer(); if (asset.loop) { audio.currentTime = loopStart; } else { @@ -73,8 +68,8 @@ export class HTMLAudioPlayer extends AudioPlayer { if (end <= audio.currentTime) { onEnded(); } else if (end <= audio.currentTime + diff) { // 次の timeupdate イベントまでに end を超えることが確定していれば、見越し時間で停止処理を行う - clearTimer(); - timerId = setTimeout(() => { + this._clearTimer(); + this._loopTimeoutId = setTimeout(() => { onEnded(); }, (end - audio.currentTime) * 1000); } @@ -139,6 +134,13 @@ export class HTMLAudioPlayer extends AudioPlayer { super.stop(); } + private _clearTimer(): void { + if (this._loopTimeoutId != null) { + clearTimeout(this._loopTimeoutId); + this._loopTimeoutId = null; + } + } + private _clearEndedEventHandler(): void { if (this._audioInstance) this._audioInstance.removeEventListener("ended", this._endedEventHandler, false); @@ -146,10 +148,7 @@ export class HTMLAudioPlayer extends AudioPlayer { clearTimeout(this._dummyDurationWaitTimer); this._dummyDurationWaitTimer = null; } - if (this._loopTimeoutId != null) { - clearTimeout(this._loopTimeoutId); - this._loopTimeoutId = null; - } + this._clearTimer(); } // audio.play() は非同期なので、 play が開始される前に stop を呼ばれた場合はこのハンドラ到達時に停止する From a989a39e44b335f31123f01ab6a0408f932f49a8 Mon Sep 17 00:00:00 2001 From: kamakiri01 Date: Thu, 11 Jan 2024 21:14:28 +0900 Subject: [PATCH 09/15] rename timer id variable --- src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts index 406e1d4..7a261ef 100644 --- a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts +++ b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts @@ -11,7 +11,7 @@ export class HTMLAudioPlayer extends AudioPlayer { private _isWaitingPlayEvent: boolean = false; private _isStopRequested: boolean = false; private _onPlayEventHandler: () => void; - private _dummyDurationWaitTimer: any; + private _dummyDurationWaitTimerId: any; private _loopTimeoutId: any; constructor(system: pdi.AudioSystem, manager: AudioManager) { @@ -23,7 +23,7 @@ export class HTMLAudioPlayer extends AudioPlayer { this._onPlayEventHandler = () => { this._onPlayEvent(); }; - this._dummyDurationWaitTimer = null; + this._dummyDurationWaitTimerId = null; } play(asset: HTMLAudioAsset): void { @@ -85,7 +85,7 @@ export class HTMLAudioPlayer extends AudioPlayer { this._audioInstance = audio; } else { // 再生できるオーディオがない場合。duration後に停止処理だけ行う(処理のみ進め音は鳴らさない) - this._dummyDurationWaitTimer = setTimeout(this._endedEventHandler, asset.duration); + this._dummyDurationWaitTimerId = setTimeout(this._endedEventHandler, asset.duration); } super.play(asset); } @@ -144,9 +144,9 @@ export class HTMLAudioPlayer extends AudioPlayer { private _clearEndedEventHandler(): void { if (this._audioInstance) this._audioInstance.removeEventListener("ended", this._endedEventHandler, false); - if (this._dummyDurationWaitTimer != null) { - clearTimeout(this._dummyDurationWaitTimer); - this._dummyDurationWaitTimer = null; + if (this._dummyDurationWaitTimerId != null) { + clearTimeout(this._dummyDurationWaitTimerId); + this._dummyDurationWaitTimerId = null; } this._clearTimer(); } From cb1d6f98ce3d7c2d7ff21925892f70b4e3281b7c Mon Sep 17 00:00:00 2001 From: kamakiri01 Date: Thu, 11 Jan 2024 21:15:42 +0900 Subject: [PATCH 10/15] rename timer id variable --- src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts index 7a261ef..73a36e7 100644 --- a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts +++ b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts @@ -55,7 +55,7 @@ export class HTMLAudioPlayer extends AudioPlayer { if (end != null) { let previousCurrentTime = 0; const onEnded = (): void => { - this._clearTimer(); + this._clearLoopTimer(); if (asset.loop) { audio.currentTime = loopStart; } else { @@ -68,7 +68,7 @@ export class HTMLAudioPlayer extends AudioPlayer { if (end <= audio.currentTime) { onEnded(); } else if (end <= audio.currentTime + diff) { // 次の timeupdate イベントまでに end を超えることが確定していれば、見越し時間で停止処理を行う - this._clearTimer(); + this._clearLoopTimer(); this._loopTimeoutId = setTimeout(() => { onEnded(); }, (end - audio.currentTime) * 1000); @@ -134,7 +134,7 @@ export class HTMLAudioPlayer extends AudioPlayer { super.stop(); } - private _clearTimer(): void { + private _clearLoopTimer(): void { if (this._loopTimeoutId != null) { clearTimeout(this._loopTimeoutId); this._loopTimeoutId = null; @@ -148,7 +148,7 @@ export class HTMLAudioPlayer extends AudioPlayer { clearTimeout(this._dummyDurationWaitTimerId); this._dummyDurationWaitTimerId = null; } - this._clearTimer(); + this._clearLoopTimer(); } // audio.play() は非同期なので、 play が開始される前に stop を呼ばれた場合はこのハンドラ到達時に停止する From fbb1b91bf333049223f03e041d9d78fbfff421f1 Mon Sep 17 00:00:00 2001 From: kamakiri01 Date: Mon, 15 Jan 2024 23:33:34 +0900 Subject: [PATCH 11/15] cover ended event after setTimeout --- src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts index 73a36e7..b652f67 100644 --- a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts +++ b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts @@ -48,6 +48,7 @@ export class HTMLAudioPlayer extends AudioPlayer { audio.addEventListener("ended", this._endedEventHandler); } else { audio.addEventListener("ended", () => { + this._clearLoopTimer(); audio.currentTime = loopStart; audio.play(); }); @@ -56,10 +57,10 @@ export class HTMLAudioPlayer extends AudioPlayer { let previousCurrentTime = 0; const onEnded = (): void => { this._clearLoopTimer(); - if (asset.loop) { - audio.currentTime = loopStart; - } else { + if (!asset.loop) { audio.pause(); + } else { + audio.currentTime = loopStart; } }; audio.addEventListener("timeupdate", () => { From fdd67e7497b737d8ba7ac90c568e9056fd2e370d Mon Sep 17 00:00:00 2001 From: kamakiri01 Date: Fri, 26 Jan 2024 21:08:33 +0900 Subject: [PATCH 12/15] fix comment --- src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts index b652f67..ace7835 100644 --- a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts +++ b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts @@ -12,7 +12,11 @@ export class HTMLAudioPlayer extends AudioPlayer { private _isStopRequested: boolean = false; private _onPlayEventHandler: () => void; private _dummyDurationWaitTimerId: any; - private _loopTimeoutId: any; + // "timeupdate" によるイベント通知間隔はシステム負荷に依存するため、 + // 次の "timeupdate" 通知タイミングより先にループすべき duration に到達してしまう可能性があり、適切なタイミングでループ処理を行うことができない。 + // そのため、 `setTimeout()` を併用することで適切なタイミングでループ処理を行う。 + // この値はそのときの timeoutID を示す。 + private _onEndedCallTimerId: any; constructor(system: pdi.AudioSystem, manager: AudioManager) { super(system); @@ -48,7 +52,7 @@ export class HTMLAudioPlayer extends AudioPlayer { audio.addEventListener("ended", this._endedEventHandler); } else { audio.addEventListener("ended", () => { - this._clearLoopTimer(); + this._clearOnEndedCallTimer(); audio.currentTime = loopStart; audio.play(); }); @@ -56,7 +60,7 @@ export class HTMLAudioPlayer extends AudioPlayer { if (end != null) { let previousCurrentTime = 0; const onEnded = (): void => { - this._clearLoopTimer(); + this._clearOnEndedCallTimer(); if (!asset.loop) { audio.pause(); } else { @@ -69,8 +73,8 @@ export class HTMLAudioPlayer extends AudioPlayer { if (end <= audio.currentTime) { onEnded(); } else if (end <= audio.currentTime + diff) { // 次の timeupdate イベントまでに end を超えることが確定していれば、見越し時間で停止処理を行う - this._clearLoopTimer(); - this._loopTimeoutId = setTimeout(() => { + this._clearOnEndedCallTimer(); + this._onEndedCallTimerId = setTimeout(() => { onEnded(); }, (end - audio.currentTime) * 1000); } @@ -135,10 +139,10 @@ export class HTMLAudioPlayer extends AudioPlayer { super.stop(); } - private _clearLoopTimer(): void { - if (this._loopTimeoutId != null) { - clearTimeout(this._loopTimeoutId); - this._loopTimeoutId = null; + private _clearOnEndedCallTimer(): void { + if (this._onEndedCallTimerId != null) { + clearTimeout(this._onEndedCallTimerId); + this._onEndedCallTimerId = null; } } @@ -149,7 +153,7 @@ export class HTMLAudioPlayer extends AudioPlayer { clearTimeout(this._dummyDurationWaitTimerId); this._dummyDurationWaitTimerId = null; } - this._clearLoopTimer(); + this._clearOnEndedCallTimer(); } // audio.play() は非同期なので、 play が開始される前に stop を呼ばれた場合はこのハンドラ到達時に停止する From e7f411c48f9d4326b0b2ac794c84ddd3afc7ba8d Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Wed, 10 Jul 2024 16:07:37 +0900 Subject: [PATCH 13/15] refactor: refactor HTMLAudioPlayer --- src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts | 155 +++--------------- .../HTMLAudioPlugin/HTMLAudioPlayerContext.ts | 142 ++++++++++++++++ 2 files changed, 162 insertions(+), 135 deletions(-) create mode 100644 src/plugin/HTMLAudioPlugin/HTMLAudioPlayerContext.ts diff --git a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts index ace7835..16a0dc2 100644 --- a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts +++ b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts @@ -2,170 +2,55 @@ import type * as pdi from "@akashic/pdi-types"; import type { AudioManager } from "../../AudioManager"; import { AudioPlayer } from "../AudioPlayer"; import type { HTMLAudioAsset } from "./HTMLAudioAsset"; -import { setupChromeMEIWorkaround } from "./HTMLAudioAutoplayHelper"; +import { HTMLAudioPlayerContext } from "./HTMLAudioPlayerContext"; export class HTMLAudioPlayer extends AudioPlayer { - private _endedEventHandler: () => void; - private _audioInstance: HTMLAudioElement | null = null; private _manager: AudioManager; - private _isWaitingPlayEvent: boolean = false; - private _isStopRequested: boolean = false; - private _onPlayEventHandler: () => void; - private _dummyDurationWaitTimerId: any; - // "timeupdate" によるイベント通知間隔はシステム負荷に依存するため、 - // 次の "timeupdate" 通知タイミングより先にループすべき duration に到達してしまう可能性があり、適切なタイミングでループ処理を行うことができない。 - // そのため、 `setTimeout()` を併用することで適切なタイミングでループ処理を行う。 - // この値はそのときの timeoutID を示す。 - private _onEndedCallTimerId: any; + private _context: HTMLAudioPlayerContext | null; constructor(system: pdi.AudioSystem, manager: AudioManager) { super(system); this._manager = manager; - this._endedEventHandler = () => { - this._onAudioEnded(); - }; - this._onPlayEventHandler = () => { - this._onPlayEvent(); - }; - this._dummyDurationWaitTimerId = null; + this._context = null; } play(asset: HTMLAudioAsset): void { - if (this.currentAudio) { - this.stop(); + if (this._context) { + if (asset.id === this._context.asset.id) { + // 同一 ID のアセットは使い回す + super.stop(); + this._context.rewind(); + super.play(asset); + return; + } + this._context.destroy(); } - const audio = asset.cloneElement(); - - if (audio) { - // NOTE: 後方互換のため、offset の指定がない場合は duration を無視 (終端まで再生) - const duration = (asset.duration != null && asset.offset != null) ? asset.duration / 1000 : null; - const offset = (asset.offset ?? 0) / 1000; - const loopStart = (asset.loop && asset.loopOffset != null) ? asset.loopOffset / 1000 : offset; - const end = (duration != null) ? offset + duration : null; - audio.currentTime = offset; - if (loopStart === 0 && end == null) { - audio.loop = asset.loop; - audio.addEventListener("ended", this._endedEventHandler); - } else { - if (!asset.loop) { - audio.addEventListener("ended", this._endedEventHandler); - } else { - audio.addEventListener("ended", () => { - this._clearOnEndedCallTimer(); - audio.currentTime = loopStart; - audio.play(); - }); - } - if (end != null) { - let previousCurrentTime = 0; - const onEnded = (): void => { - this._clearOnEndedCallTimer(); - if (!asset.loop) { - audio.pause(); - } else { - audio.currentTime = loopStart; - } - }; - audio.addEventListener("timeupdate", () => { - const diff = Math.max(0, audio.currentTime - previousCurrentTime); - previousCurrentTime = audio.currentTime; - if (end <= audio.currentTime) { - onEnded(); - } else if (end <= audio.currentTime + diff) { // 次の timeupdate イベントまでに end を超えることが確定していれば、見越し時間で停止処理を行う - this._clearOnEndedCallTimer(); - this._onEndedCallTimerId = setTimeout(() => { - onEnded(); - }, (end - audio.currentTime) * 1000); - } - }); - } - } + const context = new HTMLAudioPlayerContext({ asset }); + context.onStop.add(this.stop, this); + context.play(); + this._context = context; - setupChromeMEIWorkaround(audio); - audio.volume = this._calculateVolume(); - audio.play().catch((_err) => { /* user interactの前にplay()を呼ぶとエラーになる。これはHTMLAudioAutoplayHelperで吸収する */}); - audio.addEventListener("play", this._onPlayEventHandler, false); - this._isWaitingPlayEvent = true; - this._audioInstance = audio; - } else { - // 再生できるオーディオがない場合。duration後に停止処理だけ行う(処理のみ進め音は鳴らさない) - this._dummyDurationWaitTimerId = setTimeout(this._endedEventHandler, asset.duration); - } super.play(asset); } stop(): void { - if (!this.currentAudio) { - super.stop(); - return; - } - this._clearEndedEventHandler(); - - if (this._audioInstance) { - if (!this._isWaitingPlayEvent) { - // _audioInstance が再び play されることは無いので、 removeEventListener("play") する必要は無い - this._audioInstance.pause(); - this._audioInstance = null; - } else { - this._isStopRequested = true; - } - } + this._context?.pause(); super.stop(); } changeVolume(volume: number): void { super.changeVolume(volume); - if (this._audioInstance) { - this._audioInstance.volume = this._calculateVolume(); - } + this._context?.setVolume(this._calculateVolume()); } _changeMuted(muted: boolean): void { super._changeMuted(muted); - if (this._audioInstance) { - this._audioInstance.volume = this._calculateVolume(); - } + this._context?.setVolume(this._calculateVolume()); } notifyMasterVolumeChanged(): void { - if (this._audioInstance) { - this._audioInstance.volume = this._calculateVolume(); - } - } - - private _onAudioEnded(): void { - this._clearEndedEventHandler(); - super.stop(); - } - - private _clearOnEndedCallTimer(): void { - if (this._onEndedCallTimerId != null) { - clearTimeout(this._onEndedCallTimerId); - this._onEndedCallTimerId = null; - } - } - - private _clearEndedEventHandler(): void { - if (this._audioInstance) - this._audioInstance.removeEventListener("ended", this._endedEventHandler, false); - if (this._dummyDurationWaitTimerId != null) { - clearTimeout(this._dummyDurationWaitTimerId); - this._dummyDurationWaitTimerId = null; - } - this._clearOnEndedCallTimer(); - } - - // audio.play() は非同期なので、 play が開始される前に stop を呼ばれた場合はこのハンドラ到達時に停止する - private _onPlayEvent(): void { - if (!this._isWaitingPlayEvent) return; - this._isWaitingPlayEvent = false; - if (this._isStopRequested) { - this._isStopRequested = false; - // _audioInstance が再び play されることは無いので、 removeEventListener("play") する必要は無い - this._audioInstance?.pause(); - this._audioInstance = null; - } + this._context?.setVolume(this._calculateVolume()); } private _calculateVolume(): number { diff --git a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayerContext.ts b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayerContext.ts new file mode 100644 index 0000000..6d693d0 --- /dev/null +++ b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayerContext.ts @@ -0,0 +1,142 @@ +/* eslint-disable @typescript-eslint/typedef */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ + +import { Trigger } from "@akashic/trigger"; +import type { HTMLAudioAsset } from "./HTMLAudioAsset"; +import { setupChromeMEIWorkaround } from "./HTMLAudioAutoplayHelper"; + +export interface HTMLAudioPlayerContextParameterObject { + asset: HTMLAudioAsset; +} + +export class HTMLAudioPlayerContext { + onStop: Trigger; + + asset: HTMLAudioAsset; + element: HTMLAudioElement | null; + offsetStart: number; + offsetEnd: number; + loopOffset: number; + duration: number; + loop: boolean; + + _dummyTimerId: number | null; + _reachEndTimerId: number | null; + _previousCurrentTime: number; + + constructor({ asset }: HTMLAudioPlayerContextParameterObject) { + this.asset = asset; + this.duration = asset.duration ?? +Infinity; + this.offsetStart = asset.offset ?? 0; + this.offsetEnd = this.offsetStart + this.duration; + this.loop = !!asset.loop; + this.loopOffset = asset.loopOffset ?? 0; + this.onStop = new Trigger(); + this._dummyTimerId = null; + this._reachEndTimerId = null; + + const element = asset.cloneElement(); + if (element) { + setupChromeMEIWorkaround(element); + element.addEventListener("timeupdate", this._onTimeupdate, false); + element.addEventListener("ended", this._onEnded, false); + element.currentTime = this.offsetStart / 1000; + } else { + if (!asset.loop && asset.duration != null) { + this._setDummyTimer(this.duration); + } + } + this.element = element; + this._previousCurrentTime = element?.currentTime ?? 0; + } + + rewind() { + this.pause(); + this.setCurrentTime(this.loopOffset || this.offsetStart); + this.play(); + } + + play() { + this.element?.play().catch((_err) => { + // user interact の前に play() を呼ぶとエラーになる。これは HTMLAudioAutoplayHelper で吸収する + }); + } + + pause() { + this.element?.pause(); + this._clearRewindTimer(); + } + + setCurrentTime(offset: number) { + if (!this.element) return; + this.element.currentTime = offset / 1000; + this._previousCurrentTime = this.element.currentTime; + } + + setVolume(volume: number) { + if (!this.element) return; + this.element.volume = volume; + } + + destroy() { + this.onStop.destroy(); + const element = this.element; + if (element) { + element.removeEventListener("timeupdate", this._onTimeupdate, false); + element.removeEventListener("ended", this._onEnded, false); + } + this._clearDummyTimer(); + this._clearRewindTimer(); + } + + _setDummyTimer(duration: number) { + this._clearDummyTimer(); + this._dummyTimerId = window.setTimeout(() => this.pause(), duration); + } + + _clearDummyTimer() { + if (this._dummyTimerId == null) return; + window.clearTimeout(this._dummyTimerId); + this._dummyTimerId = null; + } + + _setRewindTimer(duration: number) { + this._clearRewindTimer(); + this._reachEndTimerId = window.setTimeout(() => this._onReachEnd(), duration); + } + + _clearRewindTimer() { + if (this._reachEndTimerId == null) return; + window.clearTimeout(this._reachEndTimerId); + this._reachEndTimerId = null; + } + + _onReachEnd() { + if (this.loop) { + this.rewind(); + } else { + this.pause(); + this.onStop.fire(); + } + + this._clearRewindTimer(); + } + + _onTimeupdate = () => { + const element = this.element!; // this.element が存在する場合にのみ呼び出される + if (element.paused) return; + + const currentOffset = element.currentTime * 1000; + const deltaSinceLastCall = Math.max(element.currentTime * 1000 - this._previousCurrentTime, 0); + const deltaUntilEnd = Math.max(this.offsetEnd - element.currentTime * 1000, 0); + this._previousCurrentTime = element.currentTime * 1000; + + if (this.offsetEnd < currentOffset + deltaSinceLastCall || this.offsetEnd <= currentOffset) { + this._setRewindTimer(deltaUntilEnd); + } + }; + + _onEnded = () => { + this._onReachEnd(); + }; +} From 88025ed466c9bfe0edfc17f51ed33a71aa546764 Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Tue, 16 Jul 2024 17:37:40 +0900 Subject: [PATCH 14/15] fix: rename class --- ...PlayerContext.ts => AudioElementPlayer.ts} | 31 +++++++++------- src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts | 37 +++++++++++-------- 2 files changed, 39 insertions(+), 29 deletions(-) rename src/plugin/HTMLAudioPlugin/{HTMLAudioPlayerContext.ts => AudioElementPlayer.ts} (84%) diff --git a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayerContext.ts b/src/plugin/HTMLAudioPlugin/AudioElementPlayer.ts similarity index 84% rename from src/plugin/HTMLAudioPlugin/HTMLAudioPlayerContext.ts rename to src/plugin/HTMLAudioPlugin/AudioElementPlayer.ts index 6d693d0..2b337e1 100644 --- a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayerContext.ts +++ b/src/plugin/HTMLAudioPlugin/AudioElementPlayer.ts @@ -2,17 +2,21 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { Trigger } from "@akashic/trigger"; -import type { HTMLAudioAsset } from "./HTMLAudioAsset"; import { setupChromeMEIWorkaround } from "./HTMLAudioAutoplayHelper"; -export interface HTMLAudioPlayerContextParameterObject { - asset: HTMLAudioAsset; +export interface AudioElementPlayerParameterObject { + id: string; + element: HTMLAudioElement | null; + duration: number; + offset: number; + loop: boolean; + loopOffset: number; } -export class HTMLAudioPlayerContext { +export class AudioElementPlayer { onStop: Trigger; - asset: HTMLAudioAsset; + id: string; element: HTMLAudioElement | null; offsetStart: number; offsetEnd: number; @@ -24,25 +28,24 @@ export class HTMLAudioPlayerContext { _reachEndTimerId: number | null; _previousCurrentTime: number; - constructor({ asset }: HTMLAudioPlayerContextParameterObject) { - this.asset = asset; - this.duration = asset.duration ?? +Infinity; - this.offsetStart = asset.offset ?? 0; - this.offsetEnd = this.offsetStart + this.duration; - this.loop = !!asset.loop; - this.loopOffset = asset.loopOffset ?? 0; + constructor({ id, element, duration, offset, loopOffset, loop }: AudioElementPlayerParameterObject) { + this.id = id; + this.duration = duration; + this.offsetStart = offset; + this.offsetEnd = offset + duration; + this.loop = loop; + this.loopOffset = loopOffset; this.onStop = new Trigger(); this._dummyTimerId = null; this._reachEndTimerId = null; - const element = asset.cloneElement(); if (element) { setupChromeMEIWorkaround(element); element.addEventListener("timeupdate", this._onTimeupdate, false); element.addEventListener("ended", this._onEnded, false); element.currentTime = this.offsetStart / 1000; } else { - if (!asset.loop && asset.duration != null) { + if (!loop && duration != null) { this._setDummyTimer(this.duration); } } diff --git a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts index 16a0dc2..15368c3 100644 --- a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts +++ b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts @@ -1,56 +1,63 @@ import type * as pdi from "@akashic/pdi-types"; import type { AudioManager } from "../../AudioManager"; import { AudioPlayer } from "../AudioPlayer"; +import { AudioElementPlayer } from "./AudioElementPlayer"; import type { HTMLAudioAsset } from "./HTMLAudioAsset"; -import { HTMLAudioPlayerContext } from "./HTMLAudioPlayerContext"; export class HTMLAudioPlayer extends AudioPlayer { private _manager: AudioManager; - private _context: HTMLAudioPlayerContext | null; + private _player: AudioElementPlayer | null; constructor(system: pdi.AudioSystem, manager: AudioManager) { super(system); this._manager = manager; - this._context = null; + this._player = null; } play(asset: HTMLAudioAsset): void { - if (this._context) { - if (asset.id === this._context.asset.id) { + if (this._player) { + if (asset.id === this._player.id) { // 同一 ID のアセットは使い回す super.stop(); - this._context.rewind(); + this._player.rewind(); super.play(asset); return; } - this._context.destroy(); + this._player.destroy(); } - const context = new HTMLAudioPlayerContext({ asset }); - context.onStop.add(this.stop, this); - context.play(); - this._context = context; + const player = new AudioElementPlayer({ + id: asset.id, + element: asset.cloneElement(), + duration: asset.duration ?? +Infinity, + offset: asset.offset ?? 0, + loop: !!asset.loop, + loopOffset: asset.loopOffset ?? 0, + }); + player.onStop.add(this.stop, this); + player.play(); + this._player = player; super.play(asset); } stop(): void { - this._context?.pause(); + this._player?.pause(); super.stop(); } changeVolume(volume: number): void { super.changeVolume(volume); - this._context?.setVolume(this._calculateVolume()); + this._player?.setVolume(this._calculateVolume()); } _changeMuted(muted: boolean): void { super._changeMuted(muted); - this._context?.setVolume(this._calculateVolume()); + this._player?.setVolume(this._calculateVolume()); } notifyMasterVolumeChanged(): void { - this._context?.setVolume(this._calculateVolume()); + this._player?.setVolume(this._calculateVolume()); } private _calculateVolume(): number { From 5c4045fe4ae885fd23399102f0c3c5cb0c77e545 Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Tue, 16 Jul 2024 17:38:13 +0900 Subject: [PATCH 15/15] fix: set initial volume --- src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts index 15368c3..03b6d49 100644 --- a/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts +++ b/src/plugin/HTMLAudioPlugin/HTMLAudioPlayer.ts @@ -34,6 +34,7 @@ export class HTMLAudioPlayer extends AudioPlayer { loop: !!asset.loop, loopOffset: asset.loopOffset ?? 0, }); + player.setVolume(this._calculateVolume()); player.onStop.add(this.stop, this); player.play(); this._player = player;