diff --git a/CHANGELOG.md b/CHANGELOG.md index 41b280c..c2bc472 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 1.6.0 - 2020-08-07 +### Changed +- Support auto reloading + ## 1.5.0 - 2020-07-09 ### Changed - Improve performance diff --git a/media/audioPreview.js b/media/audioPreview.js index 6243872..349c268 100644 --- a/media/audioPreview.js +++ b/media/audioPreview.js @@ -65,7 +65,6 @@ class Player { } onChange() { - // stop if plaing if (this.isPlaying) { this.stop(); } @@ -73,11 +72,26 @@ class Player { this.currentSec = this.seekBar.value * this.duration / 100; this.play(); } + + dispose() { + if (this.isPlaying) { + this.stop(); + } + this.button.removeEventListener("click", this.button.onclick); + this.seekBar.removeEventListener("change", this.seekBar.onchange); + this.button.textContent = "please wait..."; + this.button.disabled = true; + this.seekBar.style.display = "none"; + this.button = undefined; + this.seekBar = undefined; + } } (function () { const vscode = acquireVsCodeApi(); - let audioBuffer; + let audioBuffer, player; + const message = document.getElementById("message"); + const decodeState = document.getElementById("decode-state"); // Handle messages from the extension window.addEventListener('message', async e => { @@ -85,10 +99,13 @@ class Player { switch (type) { case "info": + if (!data) { + message.textContent = "failed to decode header: invalid"; + return; + } await showInfo(data); // do not play audio in untrusted workspace if (isTrusted === false) { - const message = document.getElementById("message"); message.textContent = "Cannot play audio in untrusted workspaces"; break; } @@ -96,6 +113,10 @@ class Player { break; case "prepare": + if (!data) { + message.textContent = "failed to decode data: invalid"; + return; + } await showPlayer(data); vscode.postMessage({ type: 'play', start: 0, end: 10000 }); break; @@ -105,16 +126,16 @@ class Player { if (audioBuffer.length <= data.end) break; vscode.postMessage({ type: 'play', start: data.end, end: data.end + 10000 }); break; + + case "reload": + await reload(); + vscode.postMessage({ type: 'ready' }); + break; + } }); async function showInfo(data) { - const message = document.getElementById("message"); - if (!data) { - message.textContent = "failed to decode header: undefined"; - return; - } - const compressFormat = { 0: "unknown", 1: "uncompressed PCM", 2: "Microsoft ADPCM", 3: "IEEE Float", 6: "a-law", 7: "mu-law", @@ -143,12 +164,6 @@ class Player { } async function showPlayer(data) { - const message = document.getElementById("message"); - if (!data) { - message.textContent = "failed to decode data: undefined"; - return; - } - try { const ac = new AudioContext({ sampleRate: data.sampleRate }); audioBuffer = ac.createBuffer(data.numberOfChannels, data.length, data.sampleRate); @@ -158,7 +173,7 @@ class Player { } // set player ui - new Player(ac, audioBuffer, data.duration); + player = new Player(ac, audioBuffer, data.duration); } catch (err) { message.textContent = "failed to prepare audioBufferSourceNode: " + err; return; @@ -166,7 +181,7 @@ class Player { } async function setData(data) { - // copy passed data.samples into audioBuffer manually, because it is once stringified(?), + // copy passed data.samples into audioBuffer manually, because it is once stringified, // and its children are not recognised as Float32Array for (let ch = 0; ch < data.numberOfChannels; ch++) { const f32a = new Float32Array(data.length); @@ -177,11 +192,17 @@ class Player { } // show progress - const decodeState = document.getElementById("decode-state"); - const progress = Math.floor(data.end * 100 / audioBuffer.length); + const progress = Math.min(Math.floor(data.end * 100 / audioBuffer.length), 100); decodeState.textContent = "decode: " + progress + "% done"; } + async function reload() { + message.textContent = ""; + decodeState.textContent = ""; + player.dispose(); + player = undefined; + } + // Signal to VS Code that the webview is initialized. vscode.postMessage({ type: 'ready' }); }()); \ No newline at end of file diff --git a/package.json b/package.json index b325fb8..52c3252 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "wav-preview", "displayName": "wav-preview", "description": "preview and play wav file in VS Code", - "version": "1.5.0", + "version": "1.6.0", "engines": { "vscode": "^1.56.2" }, diff --git a/src/audioPreviewEditor.ts b/src/audioPreviewEditor.ts index e11bdae..8f0df30 100644 --- a/src/audioPreviewEditor.ts +++ b/src/audioPreviewEditor.ts @@ -1,5 +1,5 @@ import * as vscode from "vscode"; -import { Disposable } from "./dispose"; +import { Disposable, disposeAll } from "./dispose"; import * as path from "path"; import { getNonce } from "./util"; import { WaveFile } from 'wavefile'; @@ -31,6 +31,7 @@ class AudioPreviewDocument extends Disposable implements vscode.CustomDocument { private readonly _uri: vscode.Uri; private _documentData: any; + private _fsWatcher: vscode.FileSystemWatcher; private constructor ( uri: vscode.Uri, @@ -39,11 +40,15 @@ class AudioPreviewDocument extends Disposable implements vscode.CustomDocument { super(); this._uri = uri; this._documentData = initialContent; + this._fsWatcher = vscode.workspace.createFileSystemWatcher(uri.fsPath, true, false, true); + this.onDidChange = this._fsWatcher.onDidChange; } public get uri() { return this._uri; } - public get wavHeader(): any { + public onDidChange: vscode.Event; + + public wavHeader(): any { return { fmt: this._documentData.fmt }; @@ -134,6 +139,11 @@ class AudioPreviewDocument extends Disposable implements vscode.CustomDocument { } } + public async reload() { + const fileData = await AudioPreviewDocument.readFile(this._uri); + this._documentData = new WaveFile(fileData); + } + private readonly _onDidDispose = this._register(new vscode.EventEmitter()); public readonly onDidDispose = this._onDidDispose.event; @@ -165,6 +175,8 @@ export class AudioPreviewEditorProvider implements vscode.CustomReadonlyEditorPr private static readonly viewType = 'wavPreview.audioPreview'; + private readonly webviews = new WebviewCollection(); + constructor ( private readonly _context: vscode.ExtensionContext ) { } @@ -179,6 +191,19 @@ export class AudioPreviewEditorProvider implements vscode.CustomReadonlyEditorPr openContext.backupId ); + const listeners: vscode.Disposable[] = []; + + listeners.push(document.onDidChange(async (e) => { + await document.reload(); + for (const webviewPanel of this.webviews.get(document.uri)) { + webviewPanel.webview.postMessage({ + type: "reload" + }); + } + })); + + document.onDidDispose(() => disposeAll(listeners)); + return document; } @@ -187,6 +212,9 @@ export class AudioPreviewEditorProvider implements vscode.CustomReadonlyEditorPr webviewPanel: vscode.WebviewPanel, _token: vscode.CancellationToken ): Promise { + // Add the webview to our internal set of active webviews + this.webviews.add(document.uri, webviewPanel); + // Setup initial content for the webview webviewPanel.webview.options = { enableScripts: true, @@ -199,7 +227,7 @@ export class AudioPreviewEditorProvider implements vscode.CustomReadonlyEditorPr case "ready": webviewPanel.webview.postMessage({ type: "info", - data: document.wavHeader, + data: document.wavHeader(), isTrusted: vscode.workspace.isTrusted }); break; @@ -271,3 +299,38 @@ export class AudioPreviewEditorProvider implements vscode.CustomReadonlyEditorPr `; } } + +/** + * Tracks all webviews. + */ + class WebviewCollection { + + private readonly _webviews = new Set<{ + readonly resource: string; + readonly webviewPanel: vscode.WebviewPanel; + }>(); + + /** + * Get all known webviews for a given uri. + */ + public *get(uri: vscode.Uri): Iterable { + const key = uri.toString(); + for (const entry of this._webviews) { + if (entry.resource === key) { + yield entry.webviewPanel; + } + } + } + + /** + * Add a new webview to the collection. + */ + public add(uri: vscode.Uri, webviewPanel: vscode.WebviewPanel) { + const entry = { resource: uri.toString(), webviewPanel }; + this._webviews.add(entry); + + webviewPanel.onDidDispose(() => { + this._webviews.delete(entry); + }); + } +} \ No newline at end of file