Skip to content

Commit 7c49249

Browse files
committed
chore: updates
1 parent ad35b9a commit 7c49249

109 files changed

Lines changed: 8620 additions & 6496 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/demo/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"devDependencies": {
3535
"@nativescript/android": "~8.9.0",
3636
"@nativescript/devtools": "~0.0.1",
37-
"@nativescript/ios": "9.0.3",
37+
"@nativescript/ios": "~8.9.0",
3838
"@nativescript/visionos": "~8.9.0"
3939
}
4040
}

apps/demo/src/app.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ require('@nativescript/canvas-polyfill');
55
import '@nativescript/canvas-polyfill';
66
import { Canvas } from '@nativescript/canvas';
77

8+
// for (let i = 0; i < 100_000; i++) {
9+
// const canvas = new Canvas();
10+
// }
11+
812
/*
913
1014

apps/demo/webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ module.exports = (env) => {
1616
config.resolve.alias.set('three/src', resolve(__dirname, '..', '..', 'node_modules', 'three', 'src'));
1717
//config.resolve.alias.set('three', resolve(__dirname, '..', '..', 'node_modules', 'three', 'src', 'Three.js'));
1818
// config.resolve.alias.set('three', resolve(__dirname, '..', '..', 'node_modules', 'three', 'build', 'three.module.js'));
19-
config.resolve.alias.set('three', resolve(__dirname, '..', '..', 'node_modules', 'three', 'build', 'three.webgpu.js'));
19+
config.resolve.alias.set('three/webgpu', resolve(__dirname, '..', '..', 'node_modules', 'three', 'build', 'three.webgpu.js'));
2020
config.resolve.alias.set('three/tsl', resolve(__dirname, '..', '..', 'node_modules', 'three', 'build', 'three.tsl.js'));
2121
});
2222

packages/audio-context/common.ts

Lines changed: 281 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,86 @@ import { knownFolders, Observable } from '@nativescript/core';
33
export const native_ = Symbol('[[native]]');
44
export const context_ = Symbol('[[context]]');
55
export const nativeCtor_ = Symbol('[[nativeConstructor]]');
6-
export class BaseAudioContext extends Observable {}
6+
7+
export type AudioContextState = 'suspended' | 'running' | 'closed';
8+
9+
export class AudioEventTarget extends Observable {
10+
addEventListener(type: string, callback: any, thisArg?: any) {
11+
if (typeof thisArg === 'boolean') thisArg = { capture: thisArg };
12+
let listener: any = callback;
13+
if (callback && typeof callback === 'object') {
14+
listener = callback[`on${type}`];
15+
}
16+
if (typeof listener !== 'function') return;
17+
const wasListening = this.hasListeners(type);
18+
super.addEventListener(type, listener, thisArg);
19+
if (!wasListening) this._onFirstListenerAdded?.(type);
20+
}
21+
22+
removeEventListener(type: string, callback?: any, thisArg?: any) {
23+
if (typeof thisArg === 'boolean') thisArg = { capture: thisArg };
24+
let listener: any = callback;
25+
if (callback && typeof callback === 'object') {
26+
listener = callback[`on${type}`];
27+
}
28+
if (typeof listener === 'function') {
29+
super.removeEventListener(type, listener, thisArg);
30+
} else {
31+
super.removeEventListener(type);
32+
}
33+
if (!this.hasListeners(type)) this._onLastListenerRemoved?.(type);
34+
}
35+
36+
dispatchEvent(event: { type: string; [k: string]: any }): boolean {
37+
this.notify({ ...event, eventName: event.type, object: this });
38+
return true;
39+
}
40+
41+
protected _onFirstListenerAdded?(type: string): void;
42+
43+
protected _onLastListenerRemoved?(type: string): void;
44+
}
45+
46+
export class BaseAudioContext extends AudioEventTarget {
47+
protected _state: AudioContextState = 'running';
48+
private _onstatechange: ((ev: { type: 'statechange' }) => void) | null = null;
49+
50+
get state(): AudioContextState {
51+
return this._state;
52+
}
53+
54+
get onstatechange(): ((ev: { type: 'statechange' }) => void) | null {
55+
return this._onstatechange;
56+
}
57+
58+
set onstatechange(cb: ((ev: { type: 'statechange' }) => void) | null) {
59+
if (this._onstatechange) super.removeEventListener('statechange', this._onstatechange);
60+
this._onstatechange = typeof cb === 'function' ? cb : null;
61+
if (this._onstatechange) super.addEventListener('statechange', this._onstatechange);
62+
}
63+
64+
protected _setState(next: AudioContextState) {
65+
if (this._state === next) return;
66+
this._state = next;
67+
this.dispatchEvent({ type: 'statechange', target: this });
68+
}
69+
}
70+
71+
export class AudioNodeBase extends AudioEventTarget {
72+
protected [context_]: BaseAudioContext;
73+
74+
constructor(context: BaseAudioContext) {
75+
super();
76+
if (!(context instanceof BaseAudioContext)) {
77+
throw new TypeError(`${this.constructor.name} constructor: Argument 1 does not implement interface BaseAudioContext.`);
78+
}
79+
this[context_] = context;
80+
}
81+
82+
get context(): BaseAudioContext {
83+
return this[context_];
84+
}
85+
}
786

887
export function normalizeSourcePath(source: string) {
988
let filePath = source;
@@ -20,13 +99,6 @@ export function looksLikePath(source: string): boolean {
2099
return source.indexOf('/') >= 0 || source.indexOf('file:') === 0 || source.indexOf('~') === 0;
21100
}
22101

23-
export function toUint8Array(source: ArrayBuffer | ArrayBufferView) {
24-
if (ArrayBuffer.isView(source)) {
25-
return new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
26-
}
27-
return new Uint8Array(source);
28-
}
29-
30102
export type LatencyHint = 'interactive' | 'balanced' | 'playback' | number;
31103

32104
export interface AudioContextOptions {
@@ -351,3 +423,204 @@ export class AudioListenerBase {
351423
this.upZ.value = uz;
352424
}
353425
}
426+
427+
export type DistanceModelType = 'linear' | 'inverse' | 'exponential';
428+
export type PanningModelType = 'equalpower' | 'HRTF';
429+
430+
const DISTANCE_MODEL_NAMES: DistanceModelType[] = ['linear', 'inverse', 'exponential'];
431+
const PANNING_MODEL_NAMES: PanningModelType[] = ['equalpower', 'HRTF'];
432+
433+
export function distanceModelToNumber(value: DistanceModelType | number | null | undefined): number {
434+
if (typeof value === 'number') return value;
435+
if (typeof value !== 'string') return 0;
436+
const i = DISTANCE_MODEL_NAMES.indexOf(value as DistanceModelType);
437+
return i === -1 ? 0 : i;
438+
}
439+
440+
export function distanceModelFromNumber(value: number): DistanceModelType {
441+
return DISTANCE_MODEL_NAMES[value | 0] ?? 'linear';
442+
}
443+
444+
export function panningModelToNumber(value: PanningModelType | number | null | undefined): number {
445+
if (typeof value === 'number') return value;
446+
if (typeof value !== 'string') return 0;
447+
const i = PANNING_MODEL_NAMES.indexOf(value as PanningModelType);
448+
return i === -1 ? 0 : i;
449+
}
450+
451+
export function panningModelFromNumber(value: number): PanningModelType {
452+
return PANNING_MODEL_NAMES[value | 0] ?? 'equalpower';
453+
}
454+
455+
export interface MediaElementTapProvider {
456+
attachAudioContextTap?(contextNative: any): any;
457+
detachAudioContextTap?(): void;
458+
}
459+
460+
export interface MediaElementLike extends MediaElementTapProvider {
461+
src?: string;
462+
currentTime?: number;
463+
loop?: boolean;
464+
volume?: number;
465+
play?(): Promise<void> | void;
466+
pause?(): void;
467+
addEventListener?(type: string, cb: Function, opts?: any): void;
468+
removeEventListener?(type: string, cb: Function, opts?: any): void;
469+
[k: string]: any;
470+
}
471+
472+
const PLAYER_REF_ = Symbol('[[playerRef]]');
473+
474+
export abstract class MediaElementAudioSourceBase {
475+
protected [context_]: BaseAudioContext;
476+
protected [PLAYER_REF_]: MediaElementLike;
477+
protected _internalGain: any = null;
478+
protected _activeSource: any = null;
479+
protected _decoded: any = null;
480+
protected _decodingFor: string | null = null;
481+
protected _wasMuted = false;
482+
protected _originalVolume = 1;
483+
protected _onPlayBound: () => void;
484+
protected _onPauseBound: () => void;
485+
protected _onEndedBound: () => void;
486+
protected _tapNode: any = null;
487+
protected _usingTap = false;
488+
489+
constructor(context: BaseAudioContext, mediaElement: MediaElementLike) {
490+
if (!(context instanceof BaseAudioContext)) throw new TypeError('MediaElementAudioSourceNode: invalid context');
491+
if (!mediaElement || (typeof mediaElement.play !== 'function' && typeof mediaElement.src !== 'string')) {
492+
throw new TypeError('MediaElementAudioSourceNode: requires a media element with `src` and `play()`');
493+
}
494+
this[context_] = context;
495+
this[PLAYER_REF_] = mediaElement;
496+
this._internalGain = this._createGainNode();
497+
498+
if (typeof mediaElement.attachAudioContextTap === 'function') {
499+
try {
500+
const tapNative = mediaElement.attachAudioContextTap((this.context as any).native);
501+
if (tapNative) {
502+
this._tapNode = this._wrapTapNative(tapNative);
503+
if (this._tapNode) {
504+
this._tapNode.connect(this._internalGain);
505+
this._usingTap = true;
506+
}
507+
}
508+
} catch (e) {}
509+
}
510+
511+
this._onPlayBound = () => this._handlePlay();
512+
this._onPauseBound = () => this._handlePause();
513+
this._onEndedBound = () => this._handleEnded();
514+
mediaElement.addEventListener?.('play', this._onPlayBound);
515+
mediaElement.addEventListener?.('pause', this._onPauseBound);
516+
mediaElement.addEventListener?.('ended', this._onEndedBound);
517+
}
518+
protected abstract _wrapTapNative(tapNative: any): { connect(t: any): void; disconnect(t?: any): void; stop?(): void } | null;
519+
520+
get mediaElement(): MediaElementLike {
521+
return this[PLAYER_REF_];
522+
}
523+
524+
get context(): BaseAudioContext {
525+
return this[context_];
526+
}
527+
528+
get _outputNode() {
529+
return this._internalGain;
530+
}
531+
532+
connect(target: any, output?: number, input?: number) {
533+
return this._internalGain.connect(target, output, input);
534+
}
535+
disconnect(target?: any, output?: number, input?: number) {
536+
return this._internalGain.disconnect(target, output, input);
537+
}
538+
539+
disposeMediaElementSource() {
540+
const el = this[PLAYER_REF_];
541+
try {
542+
el.removeEventListener?.('play', this._onPlayBound);
543+
el.removeEventListener?.('pause', this._onPauseBound);
544+
el.removeEventListener?.('ended', this._onEndedBound);
545+
} catch (e) {}
546+
if (this._usingTap) {
547+
try {
548+
el.detachAudioContextTap?.();
549+
} catch (e) {}
550+
try {
551+
this._tapNode?.disconnect?.();
552+
} catch (e) {}
553+
this._tapNode = null;
554+
this._usingTap = false;
555+
}
556+
this._stopActive();
557+
this._restoreVolume();
558+
}
559+
560+
protected abstract _createGainNode(): any;
561+
protected abstract _createBufferSource(buffer: any): any;
562+
protected abstract _decodeFromUrl(src: string): Promise<any>;
563+
564+
protected async _handlePlay() {
565+
if (this._usingTap) return;
566+
const el = this[PLAYER_REF_];
567+
const src = el.src ?? '';
568+
if (!src) return;
569+
try {
570+
if (this._decoded == null || this._decodingFor !== src) {
571+
this._decodingFor = src;
572+
this._decoded = await this._decodeFromUrl(src);
573+
}
574+
this._muteOriginal();
575+
this._stopActive();
576+
const source = this._createBufferSource(this._decoded);
577+
source.loop = !!el.loop;
578+
source.connect(this._internalGain);
579+
source.start();
580+
this._activeSource = source;
581+
} catch (e) {}
582+
}
583+
584+
protected _handlePause() {
585+
if (this._usingTap) return;
586+
this._stopActive();
587+
this._restoreVolume();
588+
}
589+
590+
protected _handleEnded() {
591+
if (this._usingTap) return;
592+
this._stopActive();
593+
this._restoreVolume();
594+
}
595+
596+
protected _stopActive() {
597+
const s = this._activeSource;
598+
this._activeSource = null;
599+
if (!s) return;
600+
try {
601+
s.stop?.();
602+
s.disconnect?.();
603+
} catch (e) {}
604+
}
605+
606+
protected _muteOriginal() {
607+
if (this._wasMuted) return;
608+
const el = this[PLAYER_REF_];
609+
if (typeof el.volume === 'number') {
610+
this._originalVolume = el.volume;
611+
try {
612+
el.volume = 0;
613+
this._wasMuted = true;
614+
} catch (e) {}
615+
}
616+
}
617+
618+
protected _restoreVolume() {
619+
if (!this._wasMuted) return;
620+
const el = this[PLAYER_REF_];
621+
try {
622+
el.volume = this._originalVolume;
623+
} catch (e) {}
624+
this._wasMuted = false;
625+
}
626+
}

0 commit comments

Comments
 (0)