@gjsify/webaudio 0.24.2 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/esm/audio-context.js +1 -1
- package/lib/esm/gst-decoder.js +1 -1
- package/lib/esm/gst-init.js +1 -1
- package/lib/esm/gst-player.js +1 -1
- package/lib/esm/gst-teardown.js +1 -0
- package/lib/esm/html-audio-element.js +1 -1
- package/lib/esm/index.js +1 -1
- package/lib/types/gst-init.d.ts +0 -22
- package/lib/types/gst-teardown.d.ts +13 -0
- package/lib/types/index.d.ts +1 -0
- package/package.json +5 -4
package/lib/esm/audio-context.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import{AudioNode as e}from"./audio-node.js";import{ensureGstInit as t}from"./gst-init.js";import{
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{AudioNode as e}from"./audio-node.js";import{ensureGstInit as t}from"./gst-init.js";import{stopAllPipelines as n}from"./gst-teardown.js";import{GainNode as r}from"./gain-node.js";import{AudioBufferSourceNode as i}from"./audio-buffer-source-node.js";import{AudioBuffer as a}from"./audio-buffer.js";import{AudioDestinationNode as o}from"./audio-destination-node.js";import{decodeAudioDataSync as s}from"./gst-decoder.js";import c from"gi://GLib?version=2.0";var AudioContext=class{state=`suspended`;sampleRate=44100;destination;listener={};_startTime;constructor(){t(),this._startTime=c.get_monotonic_time(),this.destination=new o}get currentTime(){return(c.get_monotonic_time()-this._startTime)/1e6}createGain(){return new r}createBufferSource(){return new i}createBuffer(e,t,n){return new a({numberOfChannels:e,length:t,sampleRate:n})}decodeAudioData(e,t,n){try{let n=s(e);return t?.(n),Promise.resolve(n)}catch(e){let t=e instanceof DOMException?e:new DOMException(`Unable to decode audio data`,`EncodingError`);return n?.(t),Promise.reject(t)}}async resume(){this.state=`running`}async suspend(){this.state=`suspended`}async close(){this.state=`closed`,n()}createAnalyser(){return{connect:()=>{},disconnect:()=>{},fftSize:2048,frequencyBinCount:1024,getByteFrequencyData:()=>{},getFloatFrequencyData:()=>{}}}createDynamicsCompressor(){return new e}createBiquadFilter(){return new e}createConvolver(){return new e}createPanner(){return new e}createStereoPanner(){return new e}addEventListener(e,t){}removeEventListener(e,t){}};export{AudioContext};
|
package/lib/esm/gst-decoder.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import{Gst as e,ensureGstInit as t,
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{Gst as e,ensureGstInit as t}from"./gst-init.js";import{stopPipeline as n,trackPipeline as r}from"./gst-teardown.js";import{AudioBuffer as i}from"./audio-buffer.js";function decodeAudioDataSync(a){if(t(),!(a instanceof ArrayBuffer)||a.byteLength===0)throw new DOMException(`Unable to decode audio data`,`EncodingError`);let o=e.parse_launch(`appsrc name=src ! decodebin ! audioconvert ! audioresample ! capsfilter caps=audio/x-raw,format=F32LE,layout=interleaved ! appsink name=sink sync=false`),s=o.get_by_name(`src`),c=o.get_by_name(`sink`);r(o),o.set_state(e.State.PLAYING);let l=[],u=0,d=0;try{let t=new Uint8Array(a);for(s.push_buffer(e.Buffer.new_wrapped(t)),s.end_of_stream();;){let t=c.try_pull_sample(2*Number(e.SECOND));if(!t)break;if(u===0){let e=t.get_caps();if(e){let t=e.get_structure(0);[,u]=t.get_int(`rate`),[,d]=t.get_int(`channels`)}}let n=t.get_buffer();if(!n)continue;let r=n.get_size();r>0&&l.push(new Uint8Array(n.extract_dup(0,r)))}}finally{n(o)}if(u===0||d===0)throw new DOMException(`Unable to decode audio data`,`EncodingError`);let f=0;for(let e of l)f+=e.length;let p=f/(4*d),m=new i({numberOfChannels:d,length:p,sampleRate:u}),h=0;for(let e of l){let t=new Float32Array(e.buffer,e.byteOffset,e.length/4),n=t.length/d;for(let e=0;e<n;e++)for(let n=0;n<d;n++)m._channelData[n][h+e]=t[e*d+n];h+=n}return m}export{decodeAudioDataSync};
|
package/lib/esm/gst-init.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import e from"gi://Gst?version=1.0";let
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import e from"gi://Gst?version=1.0";import t from"gi://GstApp?version=1.0";let n=!1;function ensureGstAppLoaded(){if(t.AppSrc===void 0)throw Error(`GstApp typelib is unavailable — install gstreamer1-plugins-base (gst-plugins-base)`)}function ensureGstInit(){n||=(e.init(null),ensureGstAppLoaded(),!0)}export{e as Gst,ensureGstInit};
|
package/lib/esm/gst-player.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import{Gst as e,ensureGstInit as t}from"./gst-init.js";import n from"
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{Gst as e,ensureGstInit as t}from"./gst-init.js";import{stopPipeline as n,trackPipeline as r}from"./gst-teardown.js";import i from"gi://GLib?version=2.0";var GstPlayer=class{_pipeline=null;_volumeElement=null;_busWatchId=null;_ended=!1;_loop;_onEnded;_audioBuffer;constructor(n){t(),this._loop=n.loop,this._onEnded=n.onEnded,this._audioBuffer=n.audioBuffer;let{audioBuffer:i,volume:a,offset:o,duration:s,playbackRate:c}=n,l=i.sampleRate,u=i.numberOfChannels,d=this._interleave(i,o,s);if(d.length===0){this._fireEnded();return}let f=`appsrc name=src caps="${`audio/x-raw,format=F32LE,rate=${l},channels=${u},layout=interleaved`}" format=3 ! audioconvert ! volume name=vol ! autoaudiosink`;this._pipeline=e.parse_launch(f),r(this._pipeline),this._volumeElement=this._pipeline.get_by_name(`vol`);let p=this._pipeline.get_by_name(`src`);this._volumeElement.set_property(`volume`,Math.max(0,Math.min(a,10)));let m=this._pipeline.get_bus();this._busWatchId=m.add_watch(0,(t,n)=>{if(n.type===e.MessageType.EOS)this._loop&&!this._ended?this._restartPlayback(p,d):this._fireEnded();else if(n.type===e.MessageType.ERROR){let[e,t]=n.parse_error();console.error(`[webaudio] GStreamer playback error: ${e?.message??`unknown`}${t?` (${t})`:``}`),this._fireEnded()}return!0});let h=e.Buffer.new_wrapped(d),g=d.length/(4*u);h.pts=0,h.duration=Math.floor(g/l*Number(e.SECOND)),p.push_buffer(h),p.end_of_stream(),c!==1&&(this._pipeline.set_state(e.State.PAUSED),this._pipeline.seek(c,e.Format.TIME,e.SeekFlags.FLUSH|e.SeekFlags.ACCURATE,e.SeekType.SET,0,e.SeekType.NONE,-1)),this._pipeline.set_state(e.State.PLAYING)}setVolume(e){this._volumeElement&&!this._ended&&this._volumeElement.set_property(`volume`,Math.max(0,Math.min(e,10)))}setLoop(e){this._loop=e}stop(){this._ended||this._fireEnded()}get ended(){return this._ended}_restartPlayback(t,n){this._pipeline&&this._pipeline.seek_simple(e.Format.TIME,e.SeekFlags.FLUSH,0)}_fireEnded(){this._ended||(this._ended=!0,this._cleanup(),this._onEnded())}_cleanup(){let e=this._pipeline;this._pipeline=null,this._volumeElement=null,this._busWatchId=null,e&&i.idle_add(i.PRIORITY_LOW,()=>(n(e),i.SOURCE_REMOVE))}_interleave(e,t,n){let r=e.numberOfChannels,i=Math.min(Math.floor(t*e.sampleRate),e.length),a=e.length-i,o=n===void 0?a:Math.min(Math.floor(n*e.sampleRate),a);if(o<=0)return new Uint8Array;let s=new Float32Array(o*r);for(let t=0;t<o;t++)for(let n=0;n<r;n++)s[t*r+n]=e._channelData[n][i+t];return new Uint8Array(s.buffer)}};export{GstPlayer};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{Gst as e}from"./gst-init.js";import t from"gi://Gio?version=2.0";const n=new Set;let r=!1,i=!1;function ensureShutdownHook(){if(!r){let e=t.Application.get_default();e&&(e.connect(`shutdown`,()=>stopAllPipelines()),r=!0)}if(!i){let e=globalThis.process;typeof e?.on==`function`&&(e.on(`exit`,()=>stopAllPipelines()),i=!0)}}function trackPipeline(e){n.add(e),ensureShutdownHook()}function stopPipeline(t){n.delete(t)&&t.set_state(e.State.NULL)}function stopAllPipelines(){for(let e of[...n])stopPipeline(e)}function livePipelineCount(){return n.size}export{livePipelineCount,stopAllPipelines,stopPipeline,trackPipeline};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import{Gst as e,ensureGstInit as t}from"./gst-init.js";const
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{Gst as e,ensureGstInit as t}from"./gst-init.js";import{stopPipeline as n,trackPipeline as r}from"./gst-teardown.js";const i=new Set([`audio/mpeg`,`audio/mp3`,`audio/wav`,`audio/x-wav`,`audio/ogg`,`audio/webm`,`audio/flac`,`audio/x-flac`,`audio/aac`,`audio/mp4`]);var HTMLAudioElement=class{src=``;volume=1;loop=!1;paused=!0;currentTime=0;duration=0;readyState=0;_pipeline=null;canPlayType(e){let t=e.split(`;`)[0].trim().toLowerCase();return i.has(t)?`maybe`:``}play(){return!this.src||(t(),this._cleanup(),this._pipeline=e.ElementFactory.make(`playbin`,`player`),!this._pipeline)?Promise.resolve():(r(this._pipeline),this._pipeline.set_property(`uri`,this.src),this._pipeline.set_property(`volume`,this.volume),this._pipeline.set_state(e.State.PLAYING),this.paused=!1,Promise.resolve())}pause(){this._pipeline&&(this._pipeline.set_state(e.State.PAUSED),this.paused=!0)}load(){this._cleanup()}addEventListener(e,t){}removeEventListener(e,t){}_cleanup(){this._pipeline&&=(n(this._pipeline),null)}};export{HTMLAudioElement};
|
package/lib/esm/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AudioNode as e}from"./audio-node.js";import{AudioParam as t}from"./audio-param.js";import{
|
|
1
|
+
import{AudioNode as e}from"./audio-node.js";import{AudioParam as t}from"./audio-param.js";import{stopAllPipelines as n}from"./gst-teardown.js";import{GainNode as r}from"./gain-node.js";import{AudioBufferSourceNode as i}from"./audio-buffer-source-node.js";import{AudioBuffer as a}from"./audio-buffer.js";import{AudioDestinationNode as o}from"./audio-destination-node.js";import{AudioContext as s}from"./audio-context.js";import{HTMLAudioElement as c}from"./html-audio-element.js";export{a as AudioBuffer,i as AudioBufferSourceNode,s as AudioContext,o as AudioDestinationNode,e as AudioNode,t as AudioParam,r as GainNode,c as HTMLAudioElement,n as stopAllPipelines};
|
package/lib/types/gst-init.d.ts
CHANGED
|
@@ -1,25 +1,3 @@
|
|
|
1
1
|
import Gst from 'gi://Gst?version=1.0';
|
|
2
2
|
export declare function ensureGstInit(): void;
|
|
3
|
-
/**
|
|
4
|
-
* Whether a GStreamer pipeline that spins its own streaming threads
|
|
5
|
-
* (`decodebin`, `playbin`, …) is UNSAFE to run on the current runtime.
|
|
6
|
-
*
|
|
7
|
-
* Under the `@gjsify/node-gi` reverse bridge, **bun** and **deno** drive the
|
|
8
|
-
* GLib main context with a pure `iterateMainContext` pump and no libuv
|
|
9
|
-
* coupling. GStreamer's own streaming threads then race the JS engine's
|
|
10
|
-
* garbage collector during pipeline teardown (a GObject finalize firing on a
|
|
11
|
-
* streaming thread while the engine drains its toggle-ref queue) and
|
|
12
|
-
* NONDETERMINISTICALLY segfault the process — reproduced on bun with the
|
|
13
|
-
* synchronous `decodebin` decode path; deno shares the same pump so is guarded
|
|
14
|
-
* defensively. GJS (native GStreamer) and Node (uv-coupled main loop) drive the
|
|
15
|
-
* identical pipeline safely, so they are NOT affected.
|
|
16
|
-
*
|
|
17
|
-
* Callers use this to fail cleanly (a rejected `EncodingError`, which every
|
|
18
|
-
* WebAudio consumer already handles) instead of crashing. This closes no
|
|
19
|
-
* working capability: GStreamer decode does not function on the node target at
|
|
20
|
-
* all (decodebin returns no samples via the reverse bridge), so the only
|
|
21
|
-
* behavior change is turning a nondeterministic crash into a clean rejection.
|
|
22
|
-
* Tracked as a node-gi GStreamer-threading follow-up.
|
|
23
|
-
*/
|
|
24
|
-
export declare function isGstStreamingUnsafe(): boolean;
|
|
25
3
|
export { Gst };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type Gst1 from '@girs/gst-1.0';
|
|
2
|
+
/** Register a pipeline as live. Call right after it is created. */
|
|
3
|
+
export declare function trackPipeline(pipeline: Gst1.Element): void;
|
|
4
|
+
/**
|
|
5
|
+
* Bring one tracked pipeline to NULL and forget it. Idempotent, and a no-op for
|
|
6
|
+
* a pipeline that some other path already tore down — which is what makes the
|
|
7
|
+
* deferred idle in `GstPlayer` safe to race against shutdown.
|
|
8
|
+
*/
|
|
9
|
+
export declare function stopPipeline(pipeline: Gst1.Element): void;
|
|
10
|
+
/** Bring every still-live pipeline to NULL. Safe to call more than once. */
|
|
11
|
+
export declare function stopAllPipelines(): void;
|
|
12
|
+
/** How many pipelines are currently tracked — for tests. */
|
|
13
|
+
export declare function livePipelineCount(): number;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -6,3 +6,4 @@ export { AudioBufferSourceNode } from './audio-buffer-source-node.js';
|
|
|
6
6
|
export { GainNode } from './gain-node.js';
|
|
7
7
|
export { AudioParam } from './audio-param.js';
|
|
8
8
|
export { HTMLAudioElement } from './html-audio-element.js';
|
|
9
|
+
export { stopAllPipelines } from './gst-teardown.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/webaudio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Web Audio API for GJS using GStreamer as audio backend",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
@@ -42,17 +42,18 @@
|
|
|
42
42
|
"audio-api"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@gjsify/dom-events": "^0.
|
|
45
|
+
"@gjsify/dom-events": "^0.26.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
+
"@girs/gio-2.0": "^4.1.0",
|
|
48
49
|
"@girs/gjs": "^4.1.0",
|
|
49
50
|
"@girs/glib-2.0": "^4.1.0",
|
|
50
51
|
"@girs/gst-1.0": "^4.1.0",
|
|
51
52
|
"@girs/gstapp-1.0": "^4.1.0",
|
|
52
53
|
"@girs/gstaudio-1.0": "^4.1.0",
|
|
53
54
|
"@girs/gstbase-1.0": "^4.1.0",
|
|
54
|
-
"@gjsify/cli": "^0.
|
|
55
|
-
"@gjsify/unit": "^0.
|
|
55
|
+
"@gjsify/cli": "^0.26.0",
|
|
56
|
+
"@gjsify/unit": "^0.26.0",
|
|
56
57
|
"@types/node": "^25.9.2",
|
|
57
58
|
"typescript": "^6.0.3"
|
|
58
59
|
},
|