@linker-design-plus/timeline-track 2.0.15 → 2.0.16

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/README.md CHANGED
@@ -66,6 +66,23 @@ timeline.on('history_change', (_event, data) => {
66
66
  });
67
67
  ```
68
68
 
69
+ ### 预览资源缓存
70
+
71
+ 预览资源缓存默认关闭。开启后,可被前端 `fetch` 读取且不超过 100MB 的 `http:` / `https:` 音视频文件会被缓存到浏览器存储中,用于后续预览播放。默认策略为 30 天 TTL、10GB 应用级容量上限、优先 OPFS,并在 OPFS 不可用时降级到 IndexedDB Blob。缓存失败会自动回退原始媒体 URL。
72
+
73
+ ```ts
74
+ const timeline = new TimelineManager({
75
+ container,
76
+ previewBackend: 'dom',
77
+ resourceCache: {
78
+ enabled: true,
79
+ resolveMode: 'prefer-fast-start'
80
+ }
81
+ });
82
+ ```
83
+
84
+ 第一版不会缓存 HLS manifest / 分片,也不会缓存超过 100MB 的媒体文件。
85
+
69
86
  ## 核心能力
70
87
 
71
88
  - 多轨时间线编辑,支持视频轨和音频轨
@@ -1,5 +1,6 @@
1
1
  import type { ActiveClipPlaybackInfo, Clip, ClipVisualTransform, PlayState, PreviewAspectRatio, PreviewMediaSource, PreviewSourceResolver, TextPreviewFontConfig, TimeMs } from '../models/types';
2
2
  import type { SourceMediaRegistry } from '../resources/sourceMediaRegistry';
3
+ import type { ResourceCacheManager } from '../resources/resourceCache';
3
4
  import type { DiagnosticsCenter, DiagnosticEmitInput } from '../../utils/diagnostics';
4
5
  export interface TimelinePreviewSyncPayload {
5
6
  activeClips: ActiveClipPlaybackInfo[];
@@ -72,6 +73,7 @@ export interface TimelinePreviewBackendOptions {
72
73
  previewSourceResolver?: PreviewSourceResolver;
73
74
  textPreviewFont?: TextPreviewFontConfig | null;
74
75
  sourceMediaRegistry?: SourceMediaRegistry;
76
+ resourceCacheManager?: ResourceCacheManager | null;
75
77
  diagnostics?: DiagnosticsCenter;
76
78
  getDiagnosticsContext?: () => Partial<DiagnosticEmitInput>;
77
79
  rootClassName?: string;
@@ -1,5 +1,6 @@
1
1
  import type { ActiveClipPlaybackInfo, ClipVisualTransform, PlayState, PreviewAspectRatio, PreviewSourceResolver, TextPreviewFontConfig, TimeMs, TrackType } from '../models/types';
2
2
  import type { SourceMediaRegistry } from '../resources/sourceMediaRegistry';
3
+ import type { ResourceCacheManager } from '../resources/resourceCache';
3
4
  import type { PreviewPendingState, PreviewRuntimeState } from './previewBackend';
4
5
  import { type DiagnosticEmitInput, type DiagnosticsCenter } from '../../utils/diagnostics';
5
6
  interface TimelinePreviewSessionCallbacks {
@@ -24,6 +25,7 @@ interface TimelinePreviewSessionDependencies {
24
25
  previewSourceResolver?: PreviewSourceResolver;
25
26
  textPreviewFont?: TextPreviewFontConfig | null;
26
27
  sourceMediaRegistry?: SourceMediaRegistry;
28
+ resourceCacheManager?: ResourceCacheManager | null;
27
29
  }
28
30
  export interface TimelinePreviewSyncPayload {
29
31
  activeClips: ActiveClipPlaybackInfo[];
@@ -94,12 +96,19 @@ export declare class TimelinePreviewSession {
94
96
  private createSlot;
95
97
  private resetSlotRecoveryTracking;
96
98
  private destroySlot;
99
+ private releaseSlotObjectUrl;
100
+ private clearPendingRuntimeSource;
101
+ private rememberSlotObjectUrl;
102
+ private revokeResolvedObjectUrl;
97
103
  private getTrackSlots;
98
104
  private swapTrackSlots;
99
105
  private applyTrackPlan;
100
106
  private applySlotTarget;
107
+ private settleResolvedSourceWithoutRecovery;
101
108
  private resolveDesiredSource;
102
109
  private decorateSlotSourceUrl;
110
+ private resolvePlayableSlotSource;
111
+ private getReusableResolvedSlotSource;
103
112
  private slotNeedsRecovery;
104
113
  private recoverSlot;
105
114
  private finishSlotRecovery;
@@ -114,7 +123,9 @@ export declare class TimelinePreviewSession {
114
123
  private scheduleDeferredPreloadFlush;
115
124
  private flushDeferredPreloads;
116
125
  private applyResolvedSlotState;
126
+ private buildCurrentTargetForSlot;
117
127
  private isCurrentRequest;
128
+ private isCurrentSourceTarget;
118
129
  private shouldIgnoreExpectedEmptied;
119
130
  private shouldIgnoreExpectedAbort;
120
131
  private shouldIgnoreClearedSlotRecoverableEvent;
@@ -1,4 +1,5 @@
1
1
  import { type TimelinePreviewBackend } from '../controllers';
2
+ import { type TimelineResourceCacheStats } from '../resources/resourceCache';
2
3
  import { TimelineConfig, Clip, ClipConfig, TimeMs, PlayState, Action, TimelineEvent, EventListener as TimelineEventListener, TimelineExportData, Track as TrackEntity, ThumbnailProvider, TrackInsertionPlacement, TrackType, ActiveClipPlaybackInfo, PreviewAspectRatio, PreviewMountConfig, PreviewBackendType, SelectedClipAudioAction, ClipConfigVoicePanelOptions } from '../models';
3
4
  import type { ResolvedPlaybackPlan } from '../controllers/timelinePlaybackResolver';
4
5
  import { TimelineClipConfigController } from '../controllers/timelineClipConfigController';
@@ -78,6 +79,7 @@ export declare class TimelineManager {
78
79
  private playbackAttemptId;
79
80
  private lastPreviewSyncedPlayState;
80
81
  private previewSyncInteractionMode;
82
+ private readonly resourceCacheManager;
81
83
  constructor(config?: Partial<TimelineConfig>);
82
84
  private createPlaybackAttemptId;
83
85
  private refreshPlaybackAttempt;
@@ -506,6 +508,9 @@ export declare class TimelineManager {
506
508
  * @returns 从 0 到最后一个音视频 clip 终点的时长(毫秒)
507
509
  */
508
510
  getTrackTotalDuration(): TimeMs;
511
+ getResourceCacheStats(): Promise<TimelineResourceCacheStats>;
512
+ cleanupResourceCache(): Promise<void>;
513
+ clearResourceCache(): Promise<void>;
509
514
  destroy(): void;
510
515
  private initKeyboardShortcuts;
511
516
  /** 清除历史堆栈 */
@@ -37,7 +37,12 @@ export interface HlsFmp4PreviewMediaSource {
37
37
  mimeType: string;
38
38
  kind: 'hls-fmp4';
39
39
  }
40
- export type PreviewMediaSource = Mp4PreviewMediaSource | HlsFmp4PreviewMediaSource;
40
+ export interface AudioPreviewMediaSource {
41
+ url: string;
42
+ mimeType: string;
43
+ kind: 'audio';
44
+ }
45
+ export type PreviewMediaSource = Mp4PreviewMediaSource | HlsFmp4PreviewMediaSource | AudioPreviewMediaSource;
41
46
  export type PreviewSourceResolver = (clip: Clip) => PreviewMediaSource | null | Promise<PreviewMediaSource | null>;
42
47
  export interface TextPreviewFontConfig {
43
48
  fontName: string;
@@ -190,6 +195,15 @@ export interface TimelineKeyboardShortcutsConfig {
190
195
  }
191
196
  export declare const defaultDarkTheme: Theme;
192
197
  export declare function resolveTheme(theme?: ThemeConfig): Theme;
198
+ export interface TimelineResourceCacheConfig {
199
+ enabled?: boolean;
200
+ maxEntryBytes?: number;
201
+ maxTotalBytes?: number;
202
+ ttlMs?: number;
203
+ preferStorage?: 'opfs' | 'indexeddb';
204
+ resolveMode?: 'prefer-fast-start' | 'prefer-cache-ready';
205
+ allowedMimeTypes?: string[];
206
+ }
193
207
  export interface TimelineConfig {
194
208
  duration: TimeMs;
195
209
  zoom: number;
@@ -206,6 +220,7 @@ export interface TimelineConfig {
206
220
  thumbnailProvider?: ThumbnailProvider;
207
221
  previewBackend?: PreviewBackendType;
208
222
  previewSourceResolver?: PreviewSourceResolver;
223
+ resourceCache?: boolean | TimelineResourceCacheConfig;
209
224
  textPreviewFont?: TextPreviewFontConfig | null;
210
225
  draftData?: TimelineExportData;
211
226
  keyboardShortcuts?: false | TimelineKeyboardShortcutsConfig;
@@ -0,0 +1,4 @@
1
+ import type { PreviewMediaSource } from '../models/types';
2
+ export declare function resolveDefaultPreviewMediaSource(source: {
3
+ src: string;
4
+ }): PreviewMediaSource | null;
@@ -0,0 +1,5 @@
1
+ import type { ResolveResourceInput, ResourceCacheConfig, ResourceCacheOptions } from './types';
2
+ export declare function normalizeResourceCacheOptions(config: ResourceCacheConfig | undefined): ResourceCacheOptions;
3
+ export declare function normalizeResourceUrl(url: string): string;
4
+ export declare function buildResourceCacheKey(input: Pick<ResolveResourceInput, 'sourceKey' | 'url'>): Promise<string>;
5
+ export declare function shouldBypassResourceCache(input: ResolveResourceInput, allowedMimeTypes?: string[]): boolean;
@@ -0,0 +1,5 @@
1
+ import type { ResourceCacheHttpClient, ResourceProbeResult } from './types';
2
+ export declare class FetchResourceCacheHttpClient implements ResourceCacheHttpClient {
3
+ probe(url: string): Promise<ResourceProbeResult>;
4
+ download(url: string): Promise<Blob>;
5
+ }
@@ -0,0 +1,7 @@
1
+ export * from './types';
2
+ export { IndexedDbResourceMetadataStore, createMemoryMetadataStore } from './metadataStore';
3
+ export { IndexedDbResourceStorage, OpfsResourceStorage, createMemoryResourceStorage, createResourceCacheStorage } from './storage';
4
+ export { buildResourceCacheKey, normalizeResourceCacheOptions, normalizeResourceUrl, shouldBypassResourceCache } from './cacheKey';
5
+ export { FetchResourceCacheHttpClient } from './http';
6
+ export { ResourceCacheManager } from './resourceCacheManager';
7
+ export type { ResourceCacheManagerDependencies } from './resourceCacheManager';
@@ -0,0 +1,11 @@
1
+ import type { CachedResourceMetadata, ResourceCacheMetadataStore } from './types';
2
+ export declare class IndexedDbResourceMetadataStore implements ResourceCacheMetadataStore {
3
+ private dbPromise;
4
+ private getDb;
5
+ get(cacheKey: string): Promise<CachedResourceMetadata | null>;
6
+ put(metadata: CachedResourceMetadata): Promise<void>;
7
+ delete(cacheKey: string): Promise<void>;
8
+ clear(): Promise<void>;
9
+ list(): Promise<CachedResourceMetadata[]>;
10
+ }
11
+ export declare function createMemoryMetadataStore(initial?: CachedResourceMetadata[]): ResourceCacheMetadataStore;
@@ -0,0 +1,39 @@
1
+ import type { ObjectUrlAdapter, ResolveResourceInput, ResolvedCachedResource, ResourceCacheConfig, ResourceCacheHttpClient, ResourceCacheMetadataStore, ResourceCacheStats, ResourceCacheStorage } from './types';
2
+ export interface ResourceCacheManagerDependencies {
3
+ options?: ResourceCacheConfig;
4
+ metadataStore?: ResourceCacheMetadataStore;
5
+ storage?: ResourceCacheStorage;
6
+ http?: ResourceCacheHttpClient;
7
+ objectUrl?: ObjectUrlAdapter;
8
+ now?: () => number;
9
+ cacheKeyBuilder?: (input: Pick<ResolveResourceInput, 'sourceKey' | 'url'>) => Promise<string>;
10
+ onEvent?: (eventName: string, data: Record<string, unknown>) => void;
11
+ }
12
+ export declare class ResourceCacheManager {
13
+ private readonly options;
14
+ private readonly metadataStore;
15
+ private readonly storagePromise;
16
+ private readonly http;
17
+ private readonly objectUrl;
18
+ private readonly now;
19
+ private readonly cacheKeyBuilder;
20
+ private readonly onEvent?;
21
+ private readonly objectUrls;
22
+ private readonly inflightDownloads;
23
+ constructor(dependencies?: ResourceCacheManagerDependencies);
24
+ resolve(input: ResolveResourceInput): Promise<ResolvedCachedResource>;
25
+ resolvePlayableUrl(input: ResolveResourceInput): Promise<ResolvedCachedResource>;
26
+ revokeObjectUrl(url: string): void;
27
+ cleanupExpired(): Promise<void>;
28
+ clear(): Promise<void>;
29
+ getStats(): Promise<ResourceCacheStats>;
30
+ private resolveExpired;
31
+ private probeCacheable;
32
+ private probeAndDownload;
33
+ private getOrCreateDownload;
34
+ private downloadAndStore;
35
+ private evictForWrite;
36
+ private deleteEntry;
37
+ private blobResult;
38
+ private emitEvent;
39
+ }
@@ -0,0 +1,24 @@
1
+ import type { ResourceCacheStorage, ResourceCacheStorageKind } from './types';
2
+ export declare class IndexedDbResourceStorage implements ResourceCacheStorage {
3
+ readonly kind: "indexeddb";
4
+ private dbPromise;
5
+ private getDb;
6
+ read(cacheKey: string): Promise<Blob | null>;
7
+ write(cacheKey: string, blob: Blob): Promise<void>;
8
+ delete(cacheKey: string): Promise<void>;
9
+ clear(): Promise<void>;
10
+ }
11
+ export declare class OpfsResourceStorage implements ResourceCacheStorage {
12
+ readonly kind: "opfs";
13
+ private directoryPromise;
14
+ private getDirectory;
15
+ ensureReady(): Promise<boolean>;
16
+ read(cacheKey: string): Promise<Blob | null>;
17
+ write(cacheKey: string, blob: Blob): Promise<void>;
18
+ delete(cacheKey: string): Promise<void>;
19
+ clear(): Promise<void>;
20
+ }
21
+ export declare function createMemoryResourceStorage(kind?: ResourceCacheStorageKind): ResourceCacheStorage;
22
+ export declare function createResourceCacheStorage(options: {
23
+ preferStorage: ResourceCacheStorageKind;
24
+ }): Promise<ResourceCacheStorage>;
@@ -0,0 +1,85 @@
1
+ export type ResourceCacheStorageKind = 'opfs' | 'indexeddb';
2
+ export type ResourceCacheResolveMode = 'prefer-fast-start' | 'prefer-cache-ready';
3
+ export type ResourceCacheStatus = 'hit' | 'miss' | 'bypass' | 'stale-revalidated' | 'fallback';
4
+ export interface ResourceCacheOptions {
5
+ enabled: boolean;
6
+ maxEntryBytes: number;
7
+ maxTotalBytes: number;
8
+ ttlMs: number;
9
+ preferStorage: ResourceCacheStorageKind;
10
+ resolveMode: ResourceCacheResolveMode;
11
+ allowedMimeTypes: string[];
12
+ }
13
+ export type ResourceCacheConfig = boolean | Partial<ResourceCacheOptions>;
14
+ export interface ResolveResourceInput {
15
+ url: string;
16
+ mimeType?: string | null;
17
+ kind?: string | null;
18
+ sourceKey?: string | null;
19
+ }
20
+ export interface ResolvedCachedResource {
21
+ url: string;
22
+ cacheStatus: ResourceCacheStatus;
23
+ sourceUrl: string;
24
+ sizeBytes?: number;
25
+ objectUrl?: string;
26
+ }
27
+ export interface ResourceProbeResult {
28
+ url: string;
29
+ ok: boolean;
30
+ status: number;
31
+ mimeType: string | null;
32
+ sizeBytes: number | null;
33
+ etag: string | null;
34
+ lastModified: string | null;
35
+ acceptRanges: string | null;
36
+ }
37
+ export interface CachedResourceMetadata {
38
+ cacheKey: string;
39
+ sourceUrl: string;
40
+ mimeType: string | null;
41
+ sizeBytes: number;
42
+ etag: string | null;
43
+ lastModified: string | null;
44
+ contentLength: number | null;
45
+ storageKind: ResourceCacheStorageKind;
46
+ createdAt: number;
47
+ updatedAt: number;
48
+ lastAccessedAt: number;
49
+ expiresAt: number;
50
+ }
51
+ export interface ResourceCacheStats {
52
+ entryCount: number;
53
+ totalBytes: number;
54
+ maxTotalBytes: number;
55
+ }
56
+ export interface TimelineResourceCacheStats extends ResourceCacheStats {
57
+ enabled: boolean;
58
+ }
59
+ export interface ResourceCacheStorage {
60
+ readonly kind: ResourceCacheStorageKind;
61
+ read(cacheKey: string): Promise<Blob | null>;
62
+ write(cacheKey: string, blob: Blob): Promise<void>;
63
+ delete(cacheKey: string): Promise<void>;
64
+ clear(): Promise<void>;
65
+ }
66
+ export interface ResourceCacheMetadataStore {
67
+ get(cacheKey: string): Promise<CachedResourceMetadata | null>;
68
+ put(metadata: CachedResourceMetadata): Promise<void>;
69
+ delete(cacheKey: string): Promise<void>;
70
+ clear(): Promise<void>;
71
+ list(): Promise<CachedResourceMetadata[]>;
72
+ }
73
+ export interface ResourceCacheHttpClient {
74
+ probe(url: string): Promise<ResourceProbeResult>;
75
+ download(url: string): Promise<Blob>;
76
+ }
77
+ export interface ObjectUrlAdapter {
78
+ createObjectURL(blob: Blob): string;
79
+ revokeObjectURL(url: string): void;
80
+ }
81
+ export declare const RESOURCE_CACHE_DEFAULT_MAX_ENTRY_BYTES: number;
82
+ export declare const RESOURCE_CACHE_DEFAULT_MAX_TOTAL_BYTES: number;
83
+ export declare const RESOURCE_CACHE_DEFAULT_TTL_MS: number;
84
+ export declare const RESOURCE_CACHE_DEFAULT_ALLOWED_MIME_TYPES: string[];
85
+ export declare const RESOURCE_CACHE_DEFAULT_OPTIONS: ResourceCacheOptions;