mirror of
https://github.com/Picovoice/porcupine.git
synced 2022-01-28 03:27:53 +03:00
add indexedDB to web binding (#612)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "$",
|
||||
"version": "2.0.2",
|
||||
"version": "2.0.3",
|
||||
"description": "Porcupine library for web browsers (via WebAssembly)",
|
||||
"author": "Picovoice Inc",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
arrayBufferToStringAtIndex,
|
||||
base64ToUint8Array,
|
||||
fetchWithTimeout,
|
||||
getRuntimeEnvironment,
|
||||
getPvStorage,
|
||||
isAccessKeyValid,
|
||||
stringHeaderToObject,
|
||||
} from './utils';
|
||||
@@ -39,8 +39,6 @@ import {
|
||||
const DEFAULT_SENSITIVITY = 0.5;
|
||||
const PV_STATUS_SUCCESS = 10000;
|
||||
|
||||
type EmptyPromise = (value: void) => void;
|
||||
|
||||
/**
|
||||
* JavaScript/WebAssembly Binding for the Picovoice Porcupine wake word engine.
|
||||
*
|
||||
@@ -83,8 +81,6 @@ export class Porcupine implements PorcupineEngine {
|
||||
private static _sampleRate: number;
|
||||
private static _version: string;
|
||||
|
||||
private static _resolvePromise: EmptyPromise | null;
|
||||
private static _rejectPromise: EmptyPromise | null;
|
||||
private static _porcupineMutex = new Mutex;
|
||||
|
||||
private constructor(handleWasm: PorcupineWasmOutput, keywordLabels: ArrayLike<string>) {
|
||||
@@ -313,25 +309,6 @@ export class Porcupine implements PorcupineEngine {
|
||||
return returnPromise;
|
||||
}
|
||||
|
||||
public static clearFilePromises(): void {
|
||||
Porcupine._rejectPromise = null;
|
||||
Porcupine._resolvePromise = null;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
public static resolveFilePromise(args: any): void {
|
||||
if (Porcupine._resolvePromise != null) {
|
||||
Porcupine._resolvePromise(args);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
public static rejectFilePromise(args: any): void {
|
||||
if (Porcupine._rejectPromise != null) {
|
||||
Porcupine._rejectPromise(args);
|
||||
}
|
||||
}
|
||||
|
||||
private static async initWasm(
|
||||
accessKey: string,
|
||||
keywordModels: ArrayLike<Uint8Array>,
|
||||
@@ -343,73 +320,13 @@ export class Porcupine implements PorcupineEngine {
|
||||
const memoryBufferInt32 = new Int32Array(memory.buffer);
|
||||
const memoryBufferFloat32 = new Float32Array(memory.buffer);
|
||||
|
||||
const storage = getPvStorage();
|
||||
|
||||
const pvConsoleLogWasm = function (index: number): void {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(arrayBufferToStringAtIndex(memoryBufferUint8, index));
|
||||
};
|
||||
|
||||
const pvFileOperationHelper = function (args: any): Promise<any> {
|
||||
let promise: any;
|
||||
const runtimeEnvironment = getRuntimeEnvironment();
|
||||
if (runtimeEnvironment === 'worker') {
|
||||
promise = new Promise((resolve, reject) => {
|
||||
Porcupine._resolvePromise = resolve;
|
||||
Porcupine._rejectPromise = reject;
|
||||
});
|
||||
self.postMessage(
|
||||
{
|
||||
command: args.command,
|
||||
path: args.path,
|
||||
content: args.content,
|
||||
},
|
||||
// @ts-ignore
|
||||
undefined
|
||||
);
|
||||
} else if (runtimeEnvironment === 'browser') {
|
||||
promise = new Promise<string>((resolve, reject) => {
|
||||
try {
|
||||
switch (args.command) {
|
||||
case 'file-save':
|
||||
localStorage.setItem(args.path, args.content);
|
||||
resolve('saved');
|
||||
break;
|
||||
case 'file-exists':
|
||||
{
|
||||
const content = localStorage.getItem(args.path);
|
||||
resolve(content as string);
|
||||
}
|
||||
break;
|
||||
case 'file-load':
|
||||
{
|
||||
const content = localStorage.getItem(args.path);
|
||||
if (content === null) {
|
||||
reject(`${args.path} does not exist`);
|
||||
} else {
|
||||
resolve(content as string);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'file-delete':
|
||||
localStorage.removeItem(args.path);
|
||||
resolve('deleted');
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`Unexpected command: ${args.command}`);
|
||||
reject();
|
||||
}
|
||||
} catch (error) {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Unexpected environment: ${runtimeEnvironment}`);
|
||||
return Promise.reject();
|
||||
}
|
||||
return promise;
|
||||
};
|
||||
|
||||
const pvAssertWasm = function (
|
||||
expr: number,
|
||||
line: number,
|
||||
@@ -520,10 +437,7 @@ export class Porcupine implements PorcupineEngine {
|
||||
): Promise<void> {
|
||||
const path = arrayBufferToStringAtIndex(memoryBufferUint8, pathAddress);
|
||||
try {
|
||||
const contentBase64 = await pvFileOperationHelper({
|
||||
command: 'file-load',
|
||||
path: path,
|
||||
});
|
||||
const contentBase64 = await storage.getItem(path);
|
||||
const contentBuffer = base64ToUint8Array(contentBase64);
|
||||
// eslint-disable-next-line
|
||||
const contentAddress = await aligned_alloc(
|
||||
@@ -565,11 +479,7 @@ export class Porcupine implements PorcupineEngine {
|
||||
contentAddress
|
||||
);
|
||||
try {
|
||||
await pvFileOperationHelper({
|
||||
command: 'file-save',
|
||||
path: path,
|
||||
content: content,
|
||||
});
|
||||
await storage.setItem(path, content);
|
||||
memoryBufferInt32[
|
||||
succeededAddress / Int32Array.BYTES_PER_ELEMENT
|
||||
] = 1;
|
||||
@@ -588,11 +498,8 @@ export class Porcupine implements PorcupineEngine {
|
||||
const path = arrayBufferToStringAtIndex(memoryBufferUint8, pathAddress);
|
||||
|
||||
try {
|
||||
const isExists = await pvFileOperationHelper({
|
||||
command: 'file-exists',
|
||||
path: path,
|
||||
});
|
||||
memoryBufferUint8[isExistsAddress] = isExists === null ? 0 : 1;
|
||||
const isExists = await storage.getItem(path);
|
||||
memoryBufferUint8[isExistsAddress] = (isExists === undefined || isExists === null) ? 0 : 1;
|
||||
memoryBufferInt32[
|
||||
succeededAddress / Int32Array.BYTES_PER_ELEMENT
|
||||
] = 1;
|
||||
@@ -609,10 +516,7 @@ export class Porcupine implements PorcupineEngine {
|
||||
): Promise<void> {
|
||||
const path = arrayBufferToStringAtIndex(memoryBufferUint8, pathAddress);
|
||||
try {
|
||||
await pvFileOperationHelper({
|
||||
command: 'file-delete',
|
||||
path: path,
|
||||
});
|
||||
await storage.removeItem(path);
|
||||
memoryBufferInt32[
|
||||
succeededAddress / Int32Array.BYTES_PER_ELEMENT
|
||||
] = 1;
|
||||
@@ -699,6 +603,7 @@ export class Porcupine implements PorcupineEngine {
|
||||
wasmCodeArray,
|
||||
importObject
|
||||
);
|
||||
|
||||
const aligned_alloc = instance.exports.aligned_alloc as CallableFunction;
|
||||
const pv_porcupine_version = instance.exports
|
||||
.pv_porcupine_version as CallableFunction;
|
||||
@@ -716,6 +621,7 @@ export class Porcupine implements PorcupineEngine {
|
||||
Int32Array.BYTES_PER_ELEMENT,
|
||||
Int32Array.BYTES_PER_ELEMENT
|
||||
);
|
||||
|
||||
if (keywordIndexAddress === 0) {
|
||||
throw new Error('malloc failed: Cannot allocate memory');
|
||||
}
|
||||
|
||||
@@ -43,25 +43,10 @@ export type PorcupineWorkerRequestInit = {
|
||||
start?: boolean;
|
||||
};
|
||||
|
||||
export type PorcupineWorkerRequestFileOperation = {
|
||||
command:
|
||||
| 'file-save-succeeded'
|
||||
| 'file-save-failed'
|
||||
| 'file-load-succeeded'
|
||||
| 'file-load-failed'
|
||||
| 'file-exists-succeeded'
|
||||
| 'file-exists-failed'
|
||||
| 'file-delete-succeeded'
|
||||
| 'file-delete-failed';
|
||||
message: string;
|
||||
content?: string;
|
||||
};
|
||||
|
||||
export type PorcupineWorkerRequest =
|
||||
| PorcupineWorkerRequestInit
|
||||
| PorcupineWorkerRequestProcess
|
||||
| PorcupineWorkerRequestVoid
|
||||
| PorcupineWorkerRequestFileOperation;
|
||||
| PorcupineWorkerRequestVoid;
|
||||
|
||||
export type PorcupineWorkerResponseReady = {
|
||||
command: 'ppn-ready';
|
||||
@@ -82,18 +67,11 @@ export type PorcupineWorkerResponseError = {
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type PorcupineWorkerResponseFileOperation = {
|
||||
command: 'file-save' | 'file-load' | 'file-exists' | 'file-delete';
|
||||
path: string;
|
||||
content?: string;
|
||||
};
|
||||
|
||||
export type PorcupineWorkerResponse =
|
||||
| PorcupineWorkerResponseReady
|
||||
| PorcupineWorkerResponseFailed
|
||||
| PorcupineWorkerResponseKeyword
|
||||
| PorcupineWorkerResponseError
|
||||
| PorcupineWorkerResponseFileOperation;
|
||||
| PorcupineWorkerResponseError;
|
||||
|
||||
export interface PorcupineEngine {
|
||||
/** Release all resources acquired by Porcupine */
|
||||
|
||||
@@ -78,55 +78,23 @@ onmessage = function (
|
||||
event: MessageEvent<PorcupineWorkerRequest>
|
||||
): void {
|
||||
switch (event.data.command) {
|
||||
case 'file-save-succeeded':
|
||||
Porcupine.resolveFilePromise(event.data.message);
|
||||
Porcupine.clearFilePromises();
|
||||
case 'init':
|
||||
init(event.data.accessKey, event.data.keywords, event.data.start);
|
||||
break;
|
||||
case 'file-save-failed':
|
||||
Porcupine.rejectFilePromise(event.data.message);
|
||||
Porcupine.clearFilePromises();
|
||||
case 'process':
|
||||
process(event.data.inputFrame);
|
||||
break;
|
||||
case 'file-load-succeeded':
|
||||
Porcupine.resolveFilePromise(event.data.content);
|
||||
Porcupine.clearFilePromises();
|
||||
case 'pause':
|
||||
paused = true;
|
||||
break;
|
||||
case 'file-load-failed':
|
||||
Porcupine.rejectFilePromise(event.data.message);
|
||||
Porcupine.clearFilePromises();
|
||||
case 'resume':
|
||||
paused = false;
|
||||
break;
|
||||
case 'file-exists-succeeded':
|
||||
Porcupine.resolveFilePromise(event.data.content);
|
||||
Porcupine.clearFilePromises();
|
||||
case 'release':
|
||||
release();
|
||||
break;
|
||||
case 'file-exists-failed':
|
||||
Porcupine.rejectFilePromise(event.data.message);
|
||||
Porcupine.clearFilePromises();
|
||||
break;
|
||||
case 'file-delete-succeeded':
|
||||
Porcupine.resolveFilePromise(event.data.message);
|
||||
Porcupine.clearFilePromises();
|
||||
break;
|
||||
case 'file-delete-failed':
|
||||
Porcupine.rejectFilePromise(event.data.message);
|
||||
Porcupine.clearFilePromises();
|
||||
break;
|
||||
case 'init':
|
||||
init(event.data.accessKey, event.data.keywords,event.data.start);
|
||||
break;
|
||||
case 'process':
|
||||
process(event.data.inputFrame);
|
||||
break;
|
||||
case 'pause':
|
||||
paused = true;
|
||||
break;
|
||||
case 'resume':
|
||||
paused = false;
|
||||
break;
|
||||
case 'release':
|
||||
release();
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Unhandled command in porcupine_worker: ' + event.data.command);
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Unhandled command in porcupine_worker: ' + event.data.command);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -76,65 +76,6 @@ export default class PorcupineWorkerFactory {
|
||||
processErrorCallback(event.data.message);
|
||||
}
|
||||
break;
|
||||
case 'file-save':
|
||||
try {
|
||||
localStorage.setItem(event.data.path, event.data.content || '');
|
||||
porcupineWorker.postMessage({
|
||||
command: 'file-save-succeeded',
|
||||
message: `Saved ${event.data.path} successfully`,
|
||||
});
|
||||
} catch (error) {
|
||||
porcupineWorker.postMessage({
|
||||
command: 'file-save-failed',
|
||||
message: `${error}`,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'file-load':
|
||||
try {
|
||||
const content = localStorage.getItem(event.data.path);
|
||||
if (content === null) {
|
||||
throw new Error('file does not exist.');
|
||||
}
|
||||
porcupineWorker.postMessage({
|
||||
command: 'file-load-succeeded',
|
||||
content: content,
|
||||
});
|
||||
} catch (error) {
|
||||
porcupineWorker.postMessage({
|
||||
command: 'file-load-failed',
|
||||
message: `${error}`,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'file-exists':
|
||||
try {
|
||||
const content = localStorage.getItem(event.data.path);
|
||||
porcupineWorker.postMessage({
|
||||
command: 'file-exists-succeeded',
|
||||
content: content,
|
||||
});
|
||||
} catch (error) {
|
||||
porcupineWorker.postMessage({
|
||||
command: 'file-exists-failed',
|
||||
message: `${error}`,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'file-delete':
|
||||
try {
|
||||
localStorage.removeItem(event.data.path);
|
||||
porcupineWorker.postMessage({
|
||||
command: 'file-delete-succeeded',
|
||||
message: `Deleted ${event.data.path} successfully`,
|
||||
});
|
||||
} catch (error) {
|
||||
porcupineWorker.postMessage({
|
||||
command: 'file-delete-failed',
|
||||
message: `${error}`,
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`Unhandled message in main.js: ${event.data}`);
|
||||
|
||||
@@ -1,14 +1,96 @@
|
||||
/*
|
||||
Copyright 2021 Picovoice Inc.
|
||||
Copyright 2021 Picovoice Inc.
|
||||
|
||||
You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
|
||||
file accompanying this source.
|
||||
You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
|
||||
file accompanying this source.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Indexed DB configurations
|
||||
*/
|
||||
const DB_NAME = 'pv_db';
|
||||
const STORE_NAME = 'pv_store';
|
||||
const V = 1;
|
||||
|
||||
/**
|
||||
* Storage Interface.
|
||||
*/
|
||||
interface PvStorage {
|
||||
setItem: (key: string, value: string) => void | Promise<void>;
|
||||
getItem: (key: string) => string | Promise<string>;
|
||||
removeItem: (key: string) => void | Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens indexedDB connection, handles version changes and gets the db instance.
|
||||
*
|
||||
* @returns The instance of indexedDB connection.
|
||||
*/
|
||||
function getDB(): Promise<IDBDatabase> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = self.indexedDB.open(DB_NAME, V);
|
||||
request.onerror = () => {
|
||||
reject(request.error);
|
||||
};
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result);
|
||||
};
|
||||
request.onupgradeneeded = () => {
|
||||
request.result.createObjectStore(STORE_NAME);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the storage to use. Either tries to use IndexedDB or localStorage.
|
||||
*
|
||||
* @returns PvStorage instance to use as storage.
|
||||
*/
|
||||
export function getPvStorage(): PvStorage {
|
||||
if (self.indexedDB) {
|
||||
const requestHelper = (request: IDBRequest): Promise<any> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onerror = () => {
|
||||
reject(request.error);
|
||||
};
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
setItem: async (key: string, value: string) => {
|
||||
const db = await getDB();
|
||||
const request = db.transaction(STORE_NAME, 'readwrite').objectStore(STORE_NAME).put(value, key);
|
||||
await requestHelper(request);
|
||||
db.close();
|
||||
},
|
||||
getItem: async (key: string) => {
|
||||
const db = await getDB();
|
||||
const request = db.transaction(STORE_NAME, 'readonly').objectStore(STORE_NAME).get(key);
|
||||
const res = await requestHelper(request);
|
||||
db.close();
|
||||
return res;
|
||||
},
|
||||
removeItem: async (key: string) => {
|
||||
const db = await getDB();
|
||||
const request = db.transaction(STORE_NAME, 'readwrite').objectStore(STORE_NAME).delete(key);
|
||||
await requestHelper(request);
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
} else if (self.localStorage) {
|
||||
return self.localStorage as PvStorage;
|
||||
}
|
||||
|
||||
throw new Error("Cannot get a presistent storage object.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a null terminated phrase stored inside an array buffer to a string
|
||||
*
|
||||
@@ -103,19 +185,6 @@ export async function fetchWithTimeout(uri: string, options = {}, time = 5000):
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Environment identifier
|
||||
*
|
||||
* @return a string containing the envirorment name
|
||||
*/
|
||||
|
||||
export function getRuntimeEnvironment(): string {
|
||||
if (typeof window === 'object' && typeof document === 'object') {
|
||||
return 'browser';
|
||||
}
|
||||
return 'worker';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking whether the given AccessKey is valid
|
||||
*
|
||||
@@ -133,4 +202,4 @@ export function getRuntimeEnvironment(): string {
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user