visualfries 0.1.4 → 0.1.6

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.
@@ -380,8 +380,7 @@ export class SceneBuilder {
380
380
  this.stateManager.removeLoadingComponent(componentId);
381
381
  }
382
382
  buildCharactersList() {
383
- // TODO - refactor
384
- this.stateManager.setCharactersList(buildCharactersListFromComponentsAndSubtitles(this.sceneData.layers, this.subtitles.data));
383
+ this.stateManager.setCharactersList(buildCharactersListFromComponentsAndSubtitles(this.sceneData.layers, this.subtitles.getSubtitlesCharactersList()));
385
384
  }
386
385
  render() {
387
386
  const rendered = this.runSync(CommandType.RENDER);
@@ -400,9 +399,10 @@ export class SceneBuilder {
400
399
  this.appManager.destroy();
401
400
  this.domManager.destroy();
402
401
  this.stateManager.destroy();
403
- this.mediaManager.destroy();
404
402
  this.timelineManager.destroy();
405
403
  this.componentsManager.destroy();
404
+ // media manages should be destroyed last
405
+ this.mediaManager.destroy();
406
406
  // Remove the container from the DI container cache
407
407
  removeContainer(this.sceneData.id);
408
408
  }
@@ -229,6 +229,16 @@ export class MediaHook {
229
229
  async #handleDestroy() {
230
230
  this.#destroyed = true;
231
231
  this.#lastTargetTime = null;
232
+ // Release the media element back to the MediaManager
233
+ if (this.#mediaElement) {
234
+ const mediaType = this.#context.type === 'VIDEO' ? 'video' : 'audio';
235
+ const source = this.#context.contextData.source;
236
+ if (source && source.url) {
237
+ this.mediaManager.releaseMediaElement(source.url, mediaType);
238
+ }
239
+ this.#context.removeResource(mediaType === 'video' ? 'videoElement' : 'audioElement');
240
+ }
241
+ this.#mediaElement = undefined;
232
242
  }
233
243
  async handle(type, context) {
234
244
  this.#context = context;
@@ -91,7 +91,9 @@ export class MediaSeekingHook {
91
91
  };
92
92
  // Add error event handling
93
93
  media.onerror = () => {
94
- console.error('Media error:', media.error);
94
+ if (media.error && media.error.code !== 4) {
95
+ console.error('Media error:', media.src, media.error);
96
+ }
95
97
  };
96
98
  }
97
99
  }
@@ -100,6 +102,7 @@ export class MediaSeekingHook {
100
102
  await this.#handleSetup();
101
103
  }
102
104
  async #handleDestroy() {
105
+ // Clear media element reference - MediaHook will handle releaseMediaElement
103
106
  this.#mediaElement = undefined;
104
107
  }
105
108
  async #handleUpdate() {
@@ -22,7 +22,7 @@ export const createGoogleFontsProvider = () => {
22
22
  const formattedFontFamily = fontFamily.replace(/\s/g, '+');
23
23
  let apiUrl = `https://fonts.googleapis.com/css2?family=${formattedFontFamily}`;
24
24
  if (text) {
25
- apiUrl += `&text=${encodeURIComponent(text)}`;
25
+ apiUrl += `&text=${text}`;
26
26
  }
27
27
  // 1. Fetch the CSS file from Google Fonts.
28
28
  const cssResponse = await fetch(apiUrl, {
@@ -181,6 +181,7 @@ export class MediaManager {
181
181
  if (type === 'video') {
182
182
  const videoElement = this.videoElements.get(mediaPath);
183
183
  if (videoElement) {
184
+ // https://html.spec.whatwg.org/multipage/media.html#best-practices-for-authors-using-media-elements
184
185
  videoElement.src = '';
185
186
  videoElement.load(); // This frees up memory
186
187
  this.videoElements.delete(mediaPath);
@@ -57,6 +57,8 @@ export class StateManager {
57
57
  return this.charactersList;
58
58
  }
59
59
  setCharactersList(chars) {
60
+ // remove duplicates
61
+ chars = [...new Set(chars)];
60
62
  this.charactersList = chars;
61
63
  }
62
64
  setScale(scale) {
@@ -208,6 +208,7 @@ export declare class SubtitlesManager {
208
208
  addNewSubtitleAfter(subtitleId: string, newText: string): void;
209
209
  splitByChars(maxChars: number): void;
210
210
  getText(): string | undefined;
211
+ getSubtitlesCharactersList(): string[];
211
212
  updateSettings(newSettings: Partial<SceneSubtitlesSettings>): void;
212
213
  findTextChunkTiming(searchText: string, options?: {
213
214
  caseSensitive?: boolean;
@@ -201,6 +201,28 @@ function buildSubtitlesManager(timeManager, eventManager, sceneData, subtitles)
201
201
  // Key format: "assetId:lang"
202
202
  // This prevents recalculating unchanged languages when editing a single language
203
203
  let validatedCollections = new Map();
204
+ function getSubtitlesCharactersList() {
205
+ // loop all subtitles in index
206
+ let charactersList = [];
207
+ for (const [assetId, assetIndex] of Object.entries(index)) {
208
+ for (const [lang, langIndex] of Object.entries(assetIndex)) {
209
+ for (const [subtitleId, subtitle] of Object.entries(langIndex)) {
210
+ const text = subtitle.text;
211
+ const characters = text.split('');
212
+ if (characters && characters.length > 0) {
213
+ for (const char of characters) {
214
+ if (!charactersList.includes(char)) {
215
+ charactersList.push(char);
216
+ }
217
+ }
218
+ }
219
+ }
220
+ }
221
+ }
222
+ // remove duplicates
223
+ charactersList = [...new Set(charactersList)];
224
+ return charactersList;
225
+ }
204
226
  // Mark subtitle as dirty (needs word validation)
205
227
  function markSubtitleDirty(subtitleId) {
206
228
  validatedSubtitles.delete(subtitleId);
@@ -514,6 +536,9 @@ function buildSubtitlesManager(timeManager, eventManager, sceneData, subtitles)
514
536
  get settings() {
515
537
  return { ...settings };
516
538
  },
539
+ getSubtitlesCharactersList() {
540
+ return getSubtitlesCharactersList();
541
+ },
517
542
  updateSettings(newSettings) {
518
543
  settings = { ...settings, ...newSettings };
519
544
  eventManager.emit('subtitlessettingschange');
@@ -1272,6 +1297,9 @@ export class SubtitlesManager {
1272
1297
  }
1273
1298
  return this.builder.getText(this.assetId, this.language);
1274
1299
  }
1300
+ getSubtitlesCharactersList() {
1301
+ return this.builder.getSubtitlesCharactersList();
1302
+ }
1275
1303
  updateSettings(newSettings) {
1276
1304
  return this.builder.updateSettings(newSettings);
1277
1305
  }
@@ -1,5 +1,5 @@
1
- import type { Appearance, SceneLayer, SubtitleCollection } from '..';
1
+ import type { Appearance, SceneLayer } from '..';
2
2
  import * as PIXI from 'pixi.js-legacy';
3
3
  export declare function changeIdDeep<T>(obj: T): T;
4
- export declare const buildCharactersListFromComponentsAndSubtitles: (layers: SceneLayer[], subtitles: Record<string, SubtitleCollection>) => string[];
4
+ export declare const buildCharactersListFromComponentsAndSubtitles: (layers: SceneLayer[], subtitlesCharactersList: string[]) => string[];
5
5
  export declare const setPlacementAndOpacity: (obj: PIXI.Sprite | PIXI.Graphics, c: Appearance) => void;
@@ -19,7 +19,7 @@ export function changeIdDeep(obj) {
19
19
  }
20
20
  return obj;
21
21
  }
22
- export const buildCharactersListFromComponentsAndSubtitles = function (layers, subtitles) {
22
+ export const buildCharactersListFromComponentsAndSubtitles = function (layers, subtitlesCharactersList) {
23
23
  const characters = ' ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-,;:!$?\'"';
24
24
  const charactestList = characters.split('');
25
25
  const components = layers.flatMap((layer) => layer.components);
@@ -42,28 +42,11 @@ export const buildCharactersListFromComponentsAndSubtitles = function (layers, s
42
42
  charactestList.push(...new Set(variantChars));
43
43
  }
44
44
  }
45
- if (component.type === 'SUBTITLES' &&
46
- component.source &&
47
- component.source?.assetId &&
48
- subtitles[component.source.assetId]) {
49
- const c = component;
50
- const source = c.source;
51
- if (source && source.assetId && subtitles[source.assetId]) {
52
- for (const langSubs of Object.values(subtitles[source.assetId])) {
53
- for (const subtitle of langSubs) {
54
- const textList = subtitle.text.split('');
55
- const missingChars = textList.filter((char) => {
56
- // Check if the character is not whitespace, not in the characters list,
57
- // and is a Unicode letter or number
58
- return !/\s/.test(char) && !characters.includes(char) && /\p{L}|\p{N}/u.test(char);
59
- });
60
- // Add both uppercase and lowercase variants of missing chars
61
- const variantChars = missingChars.flatMap((char) => [
62
- char.toLowerCase(),
63
- char.toUpperCase()
64
- ]);
65
- charactestList.push(...new Set(variantChars));
66
- }
45
+ if (subtitlesCharactersList.length > 0) {
46
+ for (const char of subtitlesCharactersList) {
47
+ if (!characters.includes(char)) {
48
+ // Add both uppercase and lowercase variants of missing chars
49
+ charactestList.push(char.toLowerCase(), char.toUpperCase());
67
50
  }
68
51
  }
69
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "visualfries",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "license": "MIT",
5
5
  "author": "ContentFries",
6
6
  "repository": {