@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 +3 -0
- package/lib/index.d.ts +102 -0
- package/lib/index.js +216 -0
- package/lib/index.js.map +1 -0
- package/package.json +62 -0
- package/src/index.ts +329 -0
- package/style/base.css +22 -0
- package/style/index.css +11 -0
- package/style/index.js +12 -0
package/README.md
ADDED
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
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
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
|
+
}
|
package/style/index.css
ADDED
|
@@ -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';
|