@jupyterlab/video-extension 4.5.0-alpha.2

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 ADDED
@@ -0,0 +1,3 @@
1
+ # video-extension
2
+
3
+ An extension for JupyterLab which provides a video file viewer.
package/lib/index.d.ts ADDED
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * @module video-extension
4
+ */
5
+ import { JupyterFrontEndPlugin } from '@jupyterlab/application';
6
+ import { ABCWidgetFactory, DocumentRegistry, DocumentWidget, IDocumentWidget } from '@jupyterlab/docregistry';
7
+ import { Contents } from '@jupyterlab/services';
8
+ import { Widget } from '@lumino/widgets';
9
+ /**
10
+ * A video viewer widget.
11
+ */
12
+ export declare class VideoViewer extends Widget {
13
+ /**
14
+ * Construct a new video viewer widget.
15
+ */
16
+ constructor(options: VideoViewer.IOptions);
17
+ /**
18
+ * Dispose of the resources held by the widget.
19
+ */
20
+ dispose(): void;
21
+ /**
22
+ * Handle `resize` messages for the widget.
23
+ */
24
+ protected onResize(msg: Widget.ResizeMessage): void;
25
+ /**
26
+ * Update the video source.
27
+ */
28
+ private _updateVideo;
29
+ private _context;
30
+ private _video;
31
+ private _contentsManager;
32
+ }
33
+ /**
34
+ * The namespace for VideoViewer class statics.
35
+ */
36
+ export declare namespace VideoViewer {
37
+ /**
38
+ * The options used to create a video viewer widget.
39
+ */
40
+ interface IOptions {
41
+ /**
42
+ * The document context for the video being rendered by the widget.
43
+ */
44
+ context: DocumentRegistry.Context;
45
+ /**
46
+ * The contents manager.
47
+ */
48
+ contentsManager: Contents.IManager;
49
+ }
50
+ }
51
+ /**
52
+ * A document widget for videos.
53
+ */
54
+ export declare class VideoDocumentWidget extends DocumentWidget<VideoViewer> {
55
+ constructor(options: VideoDocumentWidget.IOptions);
56
+ }
57
+ /**
58
+ * The namespace for VideoDocumentWidget class statics.
59
+ */
60
+ export declare namespace VideoDocumentWidget {
61
+ /**
62
+ * The options used to create a video document widget.
63
+ */
64
+ interface IOptions extends DocumentWidget.IOptions<VideoViewer> {
65
+ }
66
+ }
67
+ /**
68
+ * A widget factory for video viewers.
69
+ */
70
+ export declare class VideoViewerFactory extends ABCWidgetFactory<IDocumentWidget<VideoViewer>> {
71
+ /**
72
+ * Construct a new video viewer factory.
73
+ */
74
+ constructor(options: VideoViewerFactory.IOptions);
75
+ /**
76
+ * Create a new widget given a context.
77
+ */
78
+ protected createNewWidget(context: DocumentRegistry.Context): IDocumentWidget<VideoViewer>;
79
+ private _contentsManager;
80
+ }
81
+ /**
82
+ * The namespace for VideoViewerFactory class statics.
83
+ */
84
+ export declare namespace VideoViewerFactory {
85
+ /**
86
+ * The options used to create a video viewer factory.
87
+ */
88
+ interface IOptions extends DocumentRegistry.IWidgetFactoryOptions {
89
+ /**
90
+ * The contents manager.
91
+ */
92
+ contentsManager: Contents.IManager;
93
+ }
94
+ }
95
+ /**
96
+ * The video extension.
97
+ */
98
+ declare const plugin: JupyterFrontEndPlugin<void>;
99
+ /**
100
+ * Export the plugin as default.
101
+ */
102
+ export default plugin;
package/lib/index.js ADDED
@@ -0,0 +1,216 @@
1
+ /* -----------------------------------------------------------------------------
2
+ | Copyright (c) Jupyter Development Team.
3
+ | Distributed under the terms of the Modified BSD License.
4
+ |----------------------------------------------------------------------------*/
5
+ /**
6
+ * @packageDocumentation
7
+ * @module video-extension
8
+ */
9
+ import { ILayoutRestorer } from '@jupyterlab/application';
10
+ import { WidgetTracker } from '@jupyterlab/apputils';
11
+ import { ABCWidgetFactory, DocumentWidget } from '@jupyterlab/docregistry';
12
+ import { IDefaultDrive, RestContentProvider } from '@jupyterlab/services';
13
+ import { ITranslator } from '@jupyterlab/translation';
14
+ import { Widget } from '@lumino/widgets';
15
+ /**
16
+ * The class name added to a video viewer.
17
+ */
18
+ const VIDEO_CLASS = 'jp-VideoViewer';
19
+ /**
20
+ * The name of the factory that creates video widgets.
21
+ */
22
+ const FACTORY_VIDEO = 'VideoViewer';
23
+ /**
24
+ * Content provider ID for video streaming
25
+ */
26
+ const VIDEO_CONTENT_PROVIDER_ID = 'video-provider';
27
+ /**
28
+ * Get video file types from the document registry
29
+ */
30
+ function getVideoFileTypes(docRegistry) {
31
+ const videoFileTypes = [];
32
+ // Find all video file types
33
+ for (const fileType of docRegistry.fileTypes()) {
34
+ if (fileType.mimeTypes.some(mimeType => mimeType.startsWith('video/'))) {
35
+ videoFileTypes.push(fileType.name);
36
+ }
37
+ }
38
+ return videoFileTypes;
39
+ }
40
+ /**
41
+ * A video viewer widget.
42
+ */
43
+ export class VideoViewer extends Widget {
44
+ /**
45
+ * Construct a new video viewer widget.
46
+ */
47
+ constructor(options) {
48
+ super();
49
+ this.addClass(VIDEO_CLASS);
50
+ this._context = options.context;
51
+ this._contentsManager = options.contentsManager;
52
+ this._video = document.createElement('video');
53
+ this._video.controls = true;
54
+ this.node.appendChild(this._video);
55
+ void this._context.ready.then(() => {
56
+ void this._updateVideo();
57
+ });
58
+ this._context.model.contentChanged.connect(this._updateVideo, this);
59
+ }
60
+ /**
61
+ * Dispose of the resources held by the widget.
62
+ */
63
+ dispose() {
64
+ if (this.isDisposed) {
65
+ return;
66
+ }
67
+ super.dispose();
68
+ }
69
+ /**
70
+ * Handle `resize` messages for the widget.
71
+ */
72
+ onResize(msg) {
73
+ super.onResize(msg);
74
+ if (this._video) {
75
+ this._video.style.width = '100%';
76
+ this._video.style.height = '100%';
77
+ }
78
+ }
79
+ /**
80
+ * Update the video source.
81
+ */
82
+ async _updateVideo() {
83
+ // Use getDownloadUrl for proper URL encoding and security tokens
84
+ const videoUrl = await this._contentsManager.getDownloadUrl(this._context.path);
85
+ this._video.src = videoUrl;
86
+ }
87
+ }
88
+ /**
89
+ * A document widget for videos.
90
+ */
91
+ export class VideoDocumentWidget extends DocumentWidget {
92
+ constructor(options) {
93
+ super(options);
94
+ }
95
+ }
96
+ /**
97
+ * A widget factory for video viewers.
98
+ */
99
+ export class VideoViewerFactory extends ABCWidgetFactory {
100
+ /**
101
+ * Construct a new video viewer factory.
102
+ */
103
+ constructor(options) {
104
+ super(options);
105
+ this._contentsManager = options.contentsManager;
106
+ }
107
+ /**
108
+ * Create a new widget given a context.
109
+ */
110
+ createNewWidget(context) {
111
+ const content = new VideoViewer({
112
+ context,
113
+ contentsManager: this._contentsManager
114
+ });
115
+ const widget = new VideoDocumentWidget({
116
+ content,
117
+ context
118
+ });
119
+ return widget;
120
+ }
121
+ }
122
+ /**
123
+ * A content provider for video files.
124
+ *
125
+ * This overrides the default behavior of the RestContentProvider to not include the file content.
126
+ */
127
+ class VideoContentProvider extends RestContentProvider {
128
+ constructor(options) {
129
+ super(options);
130
+ }
131
+ /**
132
+ * Get a file or directory.
133
+ *
134
+ * @param localPath - The path to the file.
135
+ * @param options - The options used to fetch the file.
136
+ *
137
+ * @returns A promise which resolves with the file content.
138
+ */
139
+ async get(localPath, options) {
140
+ return super.get(localPath, { ...options, content: false });
141
+ }
142
+ }
143
+ /**
144
+ * The video extension.
145
+ */
146
+ const plugin = {
147
+ id: '@jupyterlab/video-extension:plugin',
148
+ description: 'Adds a viewer for video files',
149
+ autoStart: true,
150
+ requires: [ITranslator, IDefaultDrive],
151
+ optional: [ILayoutRestorer],
152
+ activate: (app, translator, defaultDrive, restorer) => {
153
+ const trans = translator.load('jupyterlab');
154
+ const { contents, serverSettings } = app.serviceManager;
155
+ // Get video file types from the document registry
156
+ const videoFileTypes = getVideoFileTypes(app.docRegistry);
157
+ // Register the video stream content provider once
158
+ const registry = defaultDrive.contentProviderRegistry;
159
+ if (registry) {
160
+ const videoContentProvider = new VideoContentProvider({
161
+ apiEndpoint: '/api/contents',
162
+ serverSettings
163
+ });
164
+ registry.register(VIDEO_CONTENT_PROVIDER_ID, videoContentProvider);
165
+ }
166
+ // Create tracker for all video widgets
167
+ const tracker = new WidgetTracker({
168
+ namespace: 'videoviewer'
169
+ });
170
+ // Create single factory for all video types
171
+ const factory = new VideoViewerFactory({
172
+ name: FACTORY_VIDEO,
173
+ label: trans.__('Video Viewer'),
174
+ fileTypes: videoFileTypes,
175
+ defaultFor: videoFileTypes,
176
+ readOnly: true,
177
+ translator,
178
+ modelName: 'base64',
179
+ contentProviderId: VIDEO_CONTENT_PROVIDER_ID,
180
+ contentsManager: contents
181
+ });
182
+ app.docRegistry.addWidgetFactory(factory);
183
+ factory.widgetCreated.connect(async (sender, widget) => {
184
+ // Track the widget
185
+ void tracker.add(widget);
186
+ // Notify the widget tracker if restore data needs to update
187
+ widget.context.pathChanged.connect(() => {
188
+ void tracker.save(widget);
189
+ });
190
+ // Set appropriate icon based on file type from document registry
191
+ const fileTypes = app.docRegistry.getFileTypesForPath(widget.context.path);
192
+ const videoFileType = fileTypes.find(ft => ft.mimeTypes.some(mimeType => mimeType.startsWith('video/')));
193
+ if (videoFileType) {
194
+ widget.title.icon = videoFileType.icon;
195
+ widget.title.iconClass = videoFileType.iconClass || '';
196
+ widget.title.iconLabel = videoFileType.iconLabel || '';
197
+ }
198
+ });
199
+ if (restorer) {
200
+ // Handle state restoration for all video types
201
+ void restorer.restore(tracker, {
202
+ command: 'docmanager:open',
203
+ args: widget => ({
204
+ path: widget.context.path,
205
+ factory: FACTORY_VIDEO
206
+ }),
207
+ name: widget => widget.context.path
208
+ });
209
+ }
210
+ }
211
+ };
212
+ /**
213
+ * Export the plugin as default.
214
+ */
215
+ export default plugin;
216
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;+EAG+E;AAC/E;;;GAGG;AAEH,OAAO,EACL,eAAe,EAGhB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EACL,gBAAgB,EAEhB,cAAc,EAEf,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAEL,aAAa,EACb,mBAAmB,EACpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC;;GAEG;AACH,MAAM,WAAW,GAAG,gBAAgB,CAAC;AAErC;;GAEG;AACH,MAAM,aAAa,GAAG,aAAa,CAAC;AAEpC;;GAEG;AACH,MAAM,yBAAyB,GAAG,gBAAgB,CAAC;AAEnD;;GAEG;AACH,SAAS,iBAAiB,CAAC,WAA6B;IACtD,MAAM,cAAc,GAAa,EAAE,CAAC;IAEpC,4BAA4B;IAC5B,KAAK,MAAM,QAAQ,IAAI,WAAW,CAAC,SAAS,EAAE,EAAE,CAAC;QAC/C,IAAI,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;YACvE,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,MAAM,OAAO,WAAY,SAAQ,MAAM;IACrC;;OAEG;IACH,YAAY,OAA6B;QACvC,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC3B,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;QAChC,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;QAEhD,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC9C,IAAI,CAAC,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC;QAE5B,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAEnC,KAAK,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE;YACjC,KAAK,IAAI,CAAC,YAAY,EAAE,CAAC;QAC3B,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IACtE,CAAC;IAED;;OAEG;IACH,OAAO;QACL,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,OAAO;QACT,CAAC;QACD,KAAK,CAAC,OAAO,EAAE,CAAC;IAClB,CAAC;IAED;;OAEG;IACO,QAAQ,CAAC,GAAyB;QAC1C,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACpB,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC;YACjC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;QACpC,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,YAAY;QACxB,iEAAiE;QACjE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,cAAc,CACzD,IAAI,CAAC,QAAQ,CAAC,IAAI,CACnB,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,QAAQ,CAAC;IAC7B,CAAC;CAKF;AAsBD;;GAEG;AACH,MAAM,OAAO,mBAAoB,SAAQ,cAA2B;IAClE,YAAY,OAAqC;QAC/C,KAAK,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;CACF;AAYD;;GAEG;AACH,MAAM,OAAO,kBAAmB,SAAQ,gBAEvC;IACC;;OAEG;IACH,YAAY,OAAoC;QAC9C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IAClD,CAAC;IAED;;OAEG;IACO,eAAe,CACvB,OAAiC;QAEjC,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC;YAC9B,OAAO;YACP,eAAe,EAAE,IAAI,CAAC,gBAAgB;SACvC,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,mBAAmB,CAAC;YACrC,OAAO;YACP,OAAO;SACR,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;CAGF;AAiBD;;;;GAIG;AACH,MAAM,oBAAqB,SAAQ,mBAAmB;IACpD,YAAY,OAAqC;QAC/C,KAAK,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,GAAG,CACP,SAAiB,EACjB,OAAgC;QAEhC,OAAO,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9D,CAAC;CACF;AAED;;GAEG;AACH,MAAM,MAAM,GAAgC;IAC1C,EAAE,EAAE,oCAAoC;IACxC,WAAW,EAAE,+BAA+B;IAC5C,SAAS,EAAE,IAAI;IACf,QAAQ,EAAE,CAAC,WAAW,EAAE,aAAa,CAAC;IACtC,QAAQ,EAAE,CAAC,eAAe,CAAC;IAC3B,QAAQ,EAAE,CACR,GAAoB,EACpB,UAAuB,EACvB,YAA6B,EAC7B,QAAgC,EAC1B,EAAE;QACR,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC5C,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,GAAG,GAAG,CAAC,cAAc,CAAC;QAExD,kDAAkD;QAClD,MAAM,cAAc,GAAG,iBAAiB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAE1D,kDAAkD;QAClD,MAAM,QAAQ,GAAG,YAAY,CAAC,uBAAuB,CAAC;QACtD,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,oBAAoB,GAAG,IAAI,oBAAoB,CAAC;gBACpD,WAAW,EAAE,eAAe;gBAC5B,cAAc;aACf,CAAC,CAAC;YACH,QAAQ,CAAC,QAAQ,CAAC,yBAAyB,EAAE,oBAAoB,CAAC,CAAC;QACrE,CAAC;QAED,uCAAuC;QACvC,MAAM,OAAO,GAAG,IAAI,aAAa,CAA+B;YAC9D,SAAS,EAAE,aAAa;SACzB,CAAC,CAAC;QAEH,4CAA4C;QAC5C,MAAM,OAAO,GAAG,IAAI,kBAAkB,CAAC;YACrC,IAAI,EAAE,aAAa;YACnB,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CAAC;YAC/B,SAAS,EAAE,cAAc;YACzB,UAAU,EAAE,cAAc;YAC1B,QAAQ,EAAE,IAAI;YACd,UAAU;YACV,SAAS,EAAE,QAAQ;YACnB,iBAAiB,EAAE,yBAAyB;YAC5C,eAAe,EAAE,QAAQ;SAC1B,CAAC,CAAC;QAEH,GAAG,CAAC,WAAW,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAE1C,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACrD,mBAAmB;YACnB,KAAK,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAEzB,4DAA4D;YAC5D,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE;gBACtC,KAAK,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC5B,CAAC,CAAC,CAAC;YAEH,iEAAiE;YACjE,MAAM,SAAS,GAAG,GAAG,CAAC,WAAW,CAAC,mBAAmB,CACnD,MAAM,CAAC,OAAO,CAAC,IAAI,CACpB,CAAC;YACF,MAAM,aAAa,GAAG,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CACxC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAC7D,CAAC;YAEF,IAAI,aAAa,EAAE,CAAC;gBAClB,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC;gBACvC,MAAM,CAAC,KAAK,CAAC,SAAS,GAAG,aAAa,CAAC,SAAS,IAAI,EAAE,CAAC;gBACvD,MAAM,CAAC,KAAK,CAAC,SAAS,GAAG,aAAa,CAAC,SAAS,IAAI,EAAE,CAAC;YACzD,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,IAAI,QAAQ,EAAE,CAAC;YACb,+CAA+C;YAC/C,KAAK,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE;gBAC7B,OAAO,EAAE,iBAAiB;gBAC1B,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;oBACf,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI;oBACzB,OAAO,EAAE,aAAa;iBACvB,CAAC;gBACF,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI;aACpC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;CACF,CAAC;AAEF;;GAEG;AACH,eAAe,MAAM,CAAC"}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@jupyterlab/video-extension",
3
+ "version": "4.5.0-alpha.2",
4
+ "description": "JupyterLab - Video File Viewer Extension",
5
+ "homepage": "https://github.com/jupyterlab/jupyterlab",
6
+ "bugs": {
7
+ "url": "https://github.com/jupyterlab/jupyterlab/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/jupyterlab/jupyterlab.git"
12
+ },
13
+ "license": "BSD-3-Clause",
14
+ "author": "Project Jupyter",
15
+ "sideEffects": [
16
+ "style/**/*"
17
+ ],
18
+ "main": "lib/index.js",
19
+ "types": "lib/index.d.ts",
20
+ "style": "style/index.css",
21
+ "directories": {
22
+ "lib": "lib/"
23
+ },
24
+ "files": [
25
+ "lib/*.{d.ts,js,js.map}",
26
+ "style/*.*",
27
+ "src/**/*.{ts,tsx}"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsc -b",
31
+ "build:test": "tsc --build tsconfig.test.json",
32
+ "clean": "rimraf lib tsconfig.tsbuildinfo",
33
+ "test": "jest",
34
+ "test:cov": "jest --collect-coverage",
35
+ "test:debug": "node --inspect-brk ../../node_modules/.bin/jest --runInBand",
36
+ "test:debug:watch": "node --inspect-brk ../../node_modules/.bin/jest --runInBand --watch",
37
+ "watch": "tsc -b --watch"
38
+ },
39
+ "dependencies": {
40
+ "@jupyterlab/application": "^4.5.0-alpha.2",
41
+ "@jupyterlab/apputils": "^4.6.0-alpha.2",
42
+ "@jupyterlab/docregistry": "^4.5.0-alpha.2",
43
+ "@jupyterlab/services": "^7.5.0-alpha.2",
44
+ "@jupyterlab/translation": "^4.5.0-alpha.2",
45
+ "@lumino/widgets": "^2.7.1"
46
+ },
47
+ "devDependencies": {
48
+ "@jupyterlab/testutils": "^4.5.0-alpha.2",
49
+ "@types/jest": "^29.2.0",
50
+ "@types/webpack-env": "^1.18.0",
51
+ "jest": "^29.2.0",
52
+ "rimraf": "~5.0.5",
53
+ "typescript": "~5.5.4"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public"
57
+ },
58
+ "jupyterlab": {
59
+ "extension": true
60
+ },
61
+ "styleModule": "style/index.js"
62
+ }
package/src/index.ts ADDED
@@ -0,0 +1,329 @@
1
+ /* -----------------------------------------------------------------------------
2
+ | Copyright (c) Jupyter Development Team.
3
+ | Distributed under the terms of the Modified BSD License.
4
+ |----------------------------------------------------------------------------*/
5
+ /**
6
+ * @packageDocumentation
7
+ * @module video-extension
8
+ */
9
+
10
+ import {
11
+ ILayoutRestorer,
12
+ JupyterFrontEnd,
13
+ JupyterFrontEndPlugin
14
+ } from '@jupyterlab/application';
15
+ import { WidgetTracker } from '@jupyterlab/apputils';
16
+ import {
17
+ ABCWidgetFactory,
18
+ DocumentRegistry,
19
+ DocumentWidget,
20
+ IDocumentWidget
21
+ } from '@jupyterlab/docregistry';
22
+ import {
23
+ Contents,
24
+ IDefaultDrive,
25
+ RestContentProvider
26
+ } from '@jupyterlab/services';
27
+ import { ITranslator } from '@jupyterlab/translation';
28
+ import { Widget } from '@lumino/widgets';
29
+
30
+ /**
31
+ * The class name added to a video viewer.
32
+ */
33
+ const VIDEO_CLASS = 'jp-VideoViewer';
34
+
35
+ /**
36
+ * The name of the factory that creates video widgets.
37
+ */
38
+ const FACTORY_VIDEO = 'VideoViewer';
39
+
40
+ /**
41
+ * Content provider ID for video streaming
42
+ */
43
+ const VIDEO_CONTENT_PROVIDER_ID = 'video-provider';
44
+
45
+ /**
46
+ * Get video file types from the document registry
47
+ */
48
+ function getVideoFileTypes(docRegistry: DocumentRegistry): string[] {
49
+ const videoFileTypes: string[] = [];
50
+
51
+ // Find all video file types
52
+ for (const fileType of docRegistry.fileTypes()) {
53
+ if (fileType.mimeTypes.some(mimeType => mimeType.startsWith('video/'))) {
54
+ videoFileTypes.push(fileType.name);
55
+ }
56
+ }
57
+
58
+ return videoFileTypes;
59
+ }
60
+
61
+ /**
62
+ * A video viewer widget.
63
+ */
64
+ export class VideoViewer extends Widget {
65
+ /**
66
+ * Construct a new video viewer widget.
67
+ */
68
+ constructor(options: VideoViewer.IOptions) {
69
+ super();
70
+ this.addClass(VIDEO_CLASS);
71
+ this._context = options.context;
72
+ this._contentsManager = options.contentsManager;
73
+
74
+ this._video = document.createElement('video');
75
+ this._video.controls = true;
76
+
77
+ this.node.appendChild(this._video);
78
+
79
+ void this._context.ready.then(() => {
80
+ void this._updateVideo();
81
+ });
82
+
83
+ this._context.model.contentChanged.connect(this._updateVideo, this);
84
+ }
85
+
86
+ /**
87
+ * Dispose of the resources held by the widget.
88
+ */
89
+ dispose(): void {
90
+ if (this.isDisposed) {
91
+ return;
92
+ }
93
+ super.dispose();
94
+ }
95
+
96
+ /**
97
+ * Handle `resize` messages for the widget.
98
+ */
99
+ protected onResize(msg: Widget.ResizeMessage): void {
100
+ super.onResize(msg);
101
+ if (this._video) {
102
+ this._video.style.width = '100%';
103
+ this._video.style.height = '100%';
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Update the video source.
109
+ */
110
+ private async _updateVideo(): Promise<void> {
111
+ // Use getDownloadUrl for proper URL encoding and security tokens
112
+ const videoUrl = await this._contentsManager.getDownloadUrl(
113
+ this._context.path
114
+ );
115
+ this._video.src = videoUrl;
116
+ }
117
+
118
+ private _context: DocumentRegistry.Context;
119
+ private _video: HTMLVideoElement;
120
+ private _contentsManager: Contents.IManager;
121
+ }
122
+
123
+ /**
124
+ * The namespace for VideoViewer class statics.
125
+ */
126
+ export namespace VideoViewer {
127
+ /**
128
+ * The options used to create a video viewer widget.
129
+ */
130
+ export interface IOptions {
131
+ /**
132
+ * The document context for the video being rendered by the widget.
133
+ */
134
+ context: DocumentRegistry.Context;
135
+
136
+ /**
137
+ * The contents manager.
138
+ */
139
+ contentsManager: Contents.IManager;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * A document widget for videos.
145
+ */
146
+ export class VideoDocumentWidget extends DocumentWidget<VideoViewer> {
147
+ constructor(options: VideoDocumentWidget.IOptions) {
148
+ super(options);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * The namespace for VideoDocumentWidget class statics.
154
+ */
155
+ export namespace VideoDocumentWidget {
156
+ /**
157
+ * The options used to create a video document widget.
158
+ */
159
+ export interface IOptions extends DocumentWidget.IOptions<VideoViewer> {}
160
+ }
161
+
162
+ /**
163
+ * A widget factory for video viewers.
164
+ */
165
+ export class VideoViewerFactory extends ABCWidgetFactory<
166
+ IDocumentWidget<VideoViewer>
167
+ > {
168
+ /**
169
+ * Construct a new video viewer factory.
170
+ */
171
+ constructor(options: VideoViewerFactory.IOptions) {
172
+ super(options);
173
+ this._contentsManager = options.contentsManager;
174
+ }
175
+
176
+ /**
177
+ * Create a new widget given a context.
178
+ */
179
+ protected createNewWidget(
180
+ context: DocumentRegistry.Context
181
+ ): IDocumentWidget<VideoViewer> {
182
+ const content = new VideoViewer({
183
+ context,
184
+ contentsManager: this._contentsManager
185
+ });
186
+ const widget = new VideoDocumentWidget({
187
+ content,
188
+ context
189
+ });
190
+ return widget;
191
+ }
192
+
193
+ private _contentsManager: Contents.IManager;
194
+ }
195
+
196
+ /**
197
+ * The namespace for VideoViewerFactory class statics.
198
+ */
199
+ export namespace VideoViewerFactory {
200
+ /**
201
+ * The options used to create a video viewer factory.
202
+ */
203
+ export interface IOptions extends DocumentRegistry.IWidgetFactoryOptions {
204
+ /**
205
+ * The contents manager.
206
+ */
207
+ contentsManager: Contents.IManager;
208
+ }
209
+ }
210
+
211
+ /**
212
+ * A content provider for video files.
213
+ *
214
+ * This overrides the default behavior of the RestContentProvider to not include the file content.
215
+ */
216
+ class VideoContentProvider extends RestContentProvider {
217
+ constructor(options: RestContentProvider.IOptions) {
218
+ super(options);
219
+ }
220
+
221
+ /**
222
+ * Get a file or directory.
223
+ *
224
+ * @param localPath - The path to the file.
225
+ * @param options - The options used to fetch the file.
226
+ *
227
+ * @returns A promise which resolves with the file content.
228
+ */
229
+ async get(
230
+ localPath: string,
231
+ options?: Contents.IFetchOptions
232
+ ): Promise<Contents.IModel> {
233
+ return super.get(localPath, { ...options, content: false });
234
+ }
235
+ }
236
+
237
+ /**
238
+ * The video extension.
239
+ */
240
+ const plugin: JupyterFrontEndPlugin<void> = {
241
+ id: '@jupyterlab/video-extension:plugin',
242
+ description: 'Adds a viewer for video files',
243
+ autoStart: true,
244
+ requires: [ITranslator, IDefaultDrive],
245
+ optional: [ILayoutRestorer],
246
+ activate: (
247
+ app: JupyterFrontEnd,
248
+ translator: ITranslator,
249
+ defaultDrive: Contents.IDrive,
250
+ restorer: ILayoutRestorer | null
251
+ ): void => {
252
+ const trans = translator.load('jupyterlab');
253
+ const { contents, serverSettings } = app.serviceManager;
254
+
255
+ // Get video file types from the document registry
256
+ const videoFileTypes = getVideoFileTypes(app.docRegistry);
257
+
258
+ // Register the video stream content provider once
259
+ const registry = defaultDrive.contentProviderRegistry;
260
+ if (registry) {
261
+ const videoContentProvider = new VideoContentProvider({
262
+ apiEndpoint: '/api/contents',
263
+ serverSettings
264
+ });
265
+ registry.register(VIDEO_CONTENT_PROVIDER_ID, videoContentProvider);
266
+ }
267
+
268
+ // Create tracker for all video widgets
269
+ const tracker = new WidgetTracker<IDocumentWidget<VideoViewer>>({
270
+ namespace: 'videoviewer'
271
+ });
272
+
273
+ // Create single factory for all video types
274
+ const factory = new VideoViewerFactory({
275
+ name: FACTORY_VIDEO,
276
+ label: trans.__('Video Viewer'),
277
+ fileTypes: videoFileTypes,
278
+ defaultFor: videoFileTypes,
279
+ readOnly: true,
280
+ translator,
281
+ modelName: 'base64',
282
+ contentProviderId: VIDEO_CONTENT_PROVIDER_ID,
283
+ contentsManager: contents
284
+ });
285
+
286
+ app.docRegistry.addWidgetFactory(factory);
287
+
288
+ factory.widgetCreated.connect(async (sender, widget) => {
289
+ // Track the widget
290
+ void tracker.add(widget);
291
+
292
+ // Notify the widget tracker if restore data needs to update
293
+ widget.context.pathChanged.connect(() => {
294
+ void tracker.save(widget);
295
+ });
296
+
297
+ // Set appropriate icon based on file type from document registry
298
+ const fileTypes = app.docRegistry.getFileTypesForPath(
299
+ widget.context.path
300
+ );
301
+ const videoFileType = fileTypes.find(ft =>
302
+ ft.mimeTypes.some(mimeType => mimeType.startsWith('video/'))
303
+ );
304
+
305
+ if (videoFileType) {
306
+ widget.title.icon = videoFileType.icon;
307
+ widget.title.iconClass = videoFileType.iconClass || '';
308
+ widget.title.iconLabel = videoFileType.iconLabel || '';
309
+ }
310
+ });
311
+
312
+ if (restorer) {
313
+ // Handle state restoration for all video types
314
+ void restorer.restore(tracker, {
315
+ command: 'docmanager:open',
316
+ args: widget => ({
317
+ path: widget.context.path,
318
+ factory: FACTORY_VIDEO
319
+ }),
320
+ name: widget => widget.context.path
321
+ });
322
+ }
323
+ }
324
+ };
325
+
326
+ /**
327
+ * Export the plugin as default.
328
+ */
329
+ export default plugin;
package/style/base.css ADDED
@@ -0,0 +1,22 @@
1
+ /*-----------------------------------------------------------------------------
2
+ | Copyright (c) Jupyter Development Team.
3
+ | Distributed under the terms of the Modified BSD License.
4
+ |----------------------------------------------------------------------------*/
5
+
6
+ /**
7
+ * Base styles for video viewer
8
+ */
9
+
10
+ .jp-RenderedVideo {
11
+ display: flex;
12
+ justify-content: center;
13
+ align-items: center;
14
+ width: 100%;
15
+ height: 100%;
16
+ }
17
+
18
+ .jp-RenderedVideo > video {
19
+ width: 100%;
20
+ height: auto;
21
+ object-fit: contain;
22
+ }
@@ -0,0 +1,11 @@
1
+ /*-----------------------------------------------------------------------------
2
+ | Copyright (c) Jupyter Development Team.
3
+ | Distributed under the terms of the Modified BSD License.
4
+ |----------------------------------------------------------------------------*/
5
+
6
+ /* This file was auto-generated by ensurePackage() in @jupyterlab/buildutils */
7
+ @import url('~@lumino/widgets/style/index.css');
8
+ @import url('~@jupyterlab/apputils/style/index.css');
9
+ @import url('~@jupyterlab/docregistry/style/index.css');
10
+ @import url('~@jupyterlab/application/style/index.css');
11
+ @import url('./base.css');
package/style/index.js ADDED
@@ -0,0 +1,12 @@
1
+ /*-----------------------------------------------------------------------------
2
+ | Copyright (c) Jupyter Development Team.
3
+ | Distributed under the terms of the Modified BSD License.
4
+ |----------------------------------------------------------------------------*/
5
+
6
+ /* This file was auto-generated by ensurePackage() in @jupyterlab/buildutils */
7
+ import '@lumino/widgets/style/index.js';
8
+ import '@jupyterlab/apputils/style/index.js';
9
+ import '@jupyterlab/docregistry/style/index.js';
10
+ import '@jupyterlab/application/style/index.js';
11
+
12
+ import './base.css';