Skip to content

Commit

Permalink
[Player] Store data without IndexedDB (#117)
Browse files Browse the repository at this point in the history
* EventSessionDB only in memory

* Don't emit play log event for never started preloads

* rm chai?

* Update packages/player/src/player/basePlayer.ts

Co-authored-by: Øyvind Smestad <[email protected]>

* Add removeOldIDB method.

---------

Co-authored-by: Øyvind Smestad <[email protected]>
  • Loading branch information
enjikaka and osmestad authored May 3, 2024
1 parent 2f27463 commit 5962d0b
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 187 deletions.
17 changes: 17 additions & 0 deletions packages/player/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,25 @@ export function bootstrap(options: Options) {
setPlayerConfig(options.players);
}

/**
* Remove the old IDB database which was used for storing events.
*/
function removeOldIDB() {
const ssuid = localStorage.getItem('ssuid');

try {
if (ssuid) {
indexedDB.deleteDatabase('streaming-sessions-' + ssuid);
}
} catch (e) {
console.warn(`DB streaming-sessions-${ssuid} could not be deleted`);
console.error(e);
}
}

mountVideoElements().then().catch(console.error);
activateVideoElements().then().catch(console.error);
removeOldIDB();

export * from './api/index';
export type * from './api/index';
Expand Down
192 changes: 5 additions & 187 deletions packages/player/src/internal/helpers/event-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,65 +7,10 @@ type MaybeEvent<P> =
| undefined;

class EventSessionDB {
// @ts-expect-error - Assigned through private method #init.
#db: IDBDatabase;
// @ts-expect-error - Assigned through private method #init.
#name: string;
#openingDatabase: Promise<void> | undefined;
#db: Map<string, any>;

constructor() {
this.#createNewDatabase().catch(console.error);
}

/**
* Create a new database and set the #openingDatabase promise to undefined when it's done.
*/
async #createNewDatabase() {
this.#openingDatabase = this.#init().then(() => {
this.#openingDatabase = undefined;
});

return this.#openingDatabase;
}

/**
* Ensure that the database is open and ready to use. Using a promise to
* debounce multiple calls to this method.
*/
async #ensureDatabase() {
let isExisting = false;

if (this.#openingDatabase) {
await this.#openingDatabase;
} else {
// Attempt to open the database
this.#openingDatabase = new Promise<void>((resolve, reject) => {
const request = window.indexedDB.open(this.#name);

// This event means the database was found and successfully opened
request.onsuccess = () => {
isExisting = true; // Database exists
resolve();
request.result.close(); // Close the database connection
};

// This event means the database does not exist and is being created
request.onupgradeneeded = () => {
isExisting = false; // Trigger database creation
};

request.onerror = () => {
reject(request.error);
};
});

await this.#openingDatabase;

// If database didn't exist, create it
if (!isExisting) {
await this.#createNewDatabase();
}
}
this.#db = new Map();
}

async #generateCompositeKey(streamingSessionId: string, eventName: string) {
Expand All @@ -81,44 +26,6 @@ class EventSessionDB {
return hashHex;
}

async #init() {
return new Promise<void>((resolve, reject) => {
this.#removeOldDatabase();

const uuid = crypto.randomUUID();
const name = 'streaming-sessions-' + uuid;
this.#name = name;
const request = indexedDB.open(name, 1);

request.onupgradeneeded = () => {
this.#db = request.result;

if (!this.#db.objectStoreNames.contains('events')) {
this.#db.createObjectStore('events', {
keyPath: 'id',
});
}
};

request.onsuccess = () => {
this.#db = request.result;
localStorage.setItem('ssuid', uuid);

resolve();
};

request.onerror = () => reject(request.error);
});
}

#removeOldDatabase() {
const ssuid = localStorage.getItem('ssuid');

if (ssuid) {
indexedDB.deleteDatabase('streaming-sessions-' + ssuid);
}
}

/**
* Delete a logged event by name and streamingSessionId.
*/
Expand All @@ -129,39 +36,12 @@ class EventSessionDB {
name: string;
streamingSessionId: string;
}): Promise<void> {
await this.#ensureDatabase();

const compositeKey = await this.#generateCompositeKey(
streamingSessionId,
name,
);

return new Promise<void>((resolve, reject) => {
try {
const transaction = this.#db.transaction(['events'], 'readwrite');
const store = transaction.objectStore('events');
const request = store.delete(compositeKey);

request.onsuccess = () => resolve();

request.onerror = () => {
throw request.error;
};
} catch (error) {
reject(error);
}
}).catch(async error => {
if (
error instanceof DOMException &&
error.message.includes('The database connection is closing')
) {
await this.#ensureDatabase();
return this.delete({
name,
streamingSessionId,
});
}
});
this.#db.delete(compositeKey);
}

/**
Expand All @@ -174,46 +54,12 @@ class EventSessionDB {
name: string;
streamingSessionId: string;
}): Promise<MaybeEvent<P>> {
await this.#ensureDatabase();

const compositeKey = await this.#generateCompositeKey(
streamingSessionId,
name,
);

return new Promise<MaybeEvent<P>>((resolve, reject) => {
try {
const transaction = this.#db.transaction(['events'], 'readonly');
const store = transaction.objectStore('events');
const request = store.get(compositeKey);

request.onsuccess = () => {
if (request.result) {
resolve(request.result as MaybeEvent<P>);
} else {
resolve(undefined);
}
};

request.onerror = () => {
throw request.error;
};
} catch (error) {
reject(error);
}
}).catch(async error => {
if (
error instanceof DOMException &&
error.message.includes('The database connection is closing')
) {
await this.#ensureDatabase();

return this.get<P>({
name,
streamingSessionId,
});
}
});
return this.#db.get(compositeKey);
}

/**
Expand All @@ -225,40 +71,12 @@ class EventSessionDB {
payload: unknown;
streamingSessionId: string;
}): Promise<void> {
await this.#ensureDatabase();

const compositeKey = await this.#generateCompositeKey(
value.streamingSessionId,
value.name,
);

return new Promise<void>((resolve, reject) => {
try {
const transaction = this.#db.transaction(['events'], 'readwrite');
const store = transaction.objectStore('events');

value.id = compositeKey;

const request = store.put(value);

request.onsuccess = () => resolve();

request.onerror = () => {
throw request.error;
};
} catch (error) {
reject(error);
}
}).catch(async error => {
if (
error instanceof DOMException &&
error.message.includes('The database connection is closing')
) {
await this.#ensureDatabase();

return this.put(value);
}
});
this.#db.set(compositeKey, value);
}
}

Expand Down
5 changes: 5 additions & 0 deletions packages/player/src/player/basePlayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,11 @@ export class BasePlayer {
}

finishCurrentMediaProduct(endReason: EndReason) {
// A media product was loaded but never started.
if (!this.hasStarted()) {
return;
}

const cssi = this.#currentStreamingSessionId;
const hasNotBeenFinished = cssi
? streamingSessionStore.hasStreamInfo(cssi)
Expand Down

0 comments on commit 5962d0b

Please sign in to comment.