Skip to content

Commit edcf6e2

Browse files
authored
Add built-in video preview (microsoft#159623)
Fixes microsoft#159106 Also hooks up our service worker to support seeking in local video resources. This requires handling range requests properly
1 parent c11dabf commit edcf6e2

File tree

9 files changed

+337
-22
lines changed

9 files changed

+337
-22
lines changed

extensions/image-preview/media/audioPreview.js

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,30 +30,38 @@
3030
container.className = 'audio-container';
3131
document.body.appendChild(container);
3232

33-
const audio = new Audio(settings.src);
33+
const audio = new Audio(settings.src === null ? undefined : settings.src);
3434
audio.controls = true;
3535

36-
audio.addEventListener('error', e => {
36+
function onLoaded() {
3737
if (hasLoadedMedia) {
3838
return;
3939
}
40-
4140
hasLoadedMedia = true;
42-
document.body.classList.add('error');
41+
4342
document.body.classList.remove('loading');
44-
});
43+
document.body.classList.add('ready');
44+
container.append(audio);
45+
}
4546

46-
audio.addEventListener('canplaythrough', () => {
47+
audio.addEventListener('error', e => {
4748
if (hasLoadedMedia) {
4849
return;
4950
}
50-
hasLoadedMedia = true;
5151

52+
hasLoadedMedia = true;
53+
document.body.classList.add('error');
5254
document.body.classList.remove('loading');
53-
document.body.classList.add('ready');
54-
container.append(audio);
5555
});
5656

57+
if (settings.src === null) {
58+
onLoaded();
59+
} else {
60+
audio.addEventListener('canplaythrough', () => {
61+
onLoaded();
62+
});
63+
}
64+
5765
document.querySelector('.open-file-link').addEventListener('click', () => {
5866
vscode.postMessage({
5967
type: 'reopen-as-text',
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
html, body {
7+
width: 100%;
8+
height: 100%;
9+
text-align: center;
10+
}
11+
12+
body {
13+
padding: 5px 10px;
14+
box-sizing: border-box;
15+
-webkit-user-select: none;
16+
user-select: none;
17+
}
18+
19+
.video-container {
20+
height: 100%;
21+
display: flex;
22+
justify-content: center;
23+
align-items: center;
24+
}
25+
26+
.container.loading,
27+
.container.error {
28+
display: flex;
29+
justify-content: center;
30+
align-items: center;
31+
}
32+
33+
.loading-indicator {
34+
width: 30px;
35+
height: 30px;
36+
background-image: url('./loading.svg');
37+
background-size: cover;
38+
}
39+
40+
.loading-indicator,
41+
.loading-error {
42+
display: none;
43+
}
44+
45+
.loading .loading-indicator,
46+
.error .loading-error {
47+
display: block;
48+
}
49+
50+
.loading-error {
51+
margin: 1em;
52+
}
53+
54+
.vscode-dark .loading-indicator {
55+
background-image: url('./loading-dark.svg');
56+
}
57+
58+
.vscode-high-contrast .loading-indicator {
59+
background-image: url('./loading-hc.svg');
60+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
// @ts-check
6+
"use strict";
7+
8+
(function () {
9+
const vscode = acquireVsCodeApi();
10+
11+
function getSettings() {
12+
const element = document.getElementById('settings');
13+
if (element) {
14+
const data = element.getAttribute('data-settings');
15+
if (data) {
16+
return JSON.parse(data);
17+
}
18+
}
19+
20+
throw new Error(`Could not load settings`);
21+
}
22+
23+
const settings = getSettings();
24+
25+
// State
26+
let hasLoadedMedia = false;
27+
28+
// Elements
29+
const container = document.createElement('div');
30+
container.className = 'video-container';
31+
document.body.appendChild(container);
32+
33+
const video = document.createElement('video');
34+
if (settings.src !== null) {
35+
video.src = settings.src;
36+
}
37+
video.controls = true;
38+
39+
function onLoaded() {
40+
if (hasLoadedMedia) {
41+
return;
42+
}
43+
hasLoadedMedia = true;
44+
45+
document.body.classList.remove('loading');
46+
document.body.classList.add('ready');
47+
container.append(video);
48+
}
49+
50+
video.addEventListener('error', e => {
51+
if (hasLoadedMedia) {
52+
return;
53+
}
54+
55+
hasLoadedMedia = true;
56+
document.body.classList.add('error');
57+
document.body.classList.remove('loading');
58+
});
59+
60+
if (settings.src === null) {
61+
onLoaded();
62+
} else {
63+
video.addEventListener('canplaythrough', () => {
64+
onLoaded();
65+
});
66+
}
67+
68+
document.querySelector('.open-file-link').addEventListener('click', () => {
69+
vscode.postMessage({
70+
type: 'reopen-as-text',
71+
});
72+
});
73+
}());

extensions/image-preview/package.json

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"onCustomEditor:imagePreview.previewEditor",
2424
"onCommand:imagePreview.zoomIn",
2525
"onCommand:imagePreview.zoomOut",
26-
"onCustomEditor:vscode.mediaPreview.audioView"
26+
"onCustomEditor:vscode.audioPreview",
27+
"onCustomEditor:vscode.videoPreview"
2728
],
2829
"capabilities": {
2930
"virtualWorkspaces": true,
@@ -35,7 +36,7 @@
3536
"customEditors": [
3637
{
3738
"viewType": "imagePreview.previewEditor",
38-
"displayName": "%customEditors.displayName%",
39+
"displayName": "%customEditor.imagePreview.displayName%",
3940
"priority": "builtin",
4041
"selector": [
4142
{
@@ -44,14 +45,24 @@
4445
]
4546
},
4647
{
47-
"viewType": "vscode.mediaPreview.audioView",
48-
"displayName": "%customEditors.displayName%",
48+
"viewType": "vscode.audioPreview",
49+
"displayName": "%customEditor.audioPreview.displayName%",
4950
"priority": "builtin",
5051
"selector": [
5152
{
5253
"filenamePattern": "*.{mp3,wav,opus,aac}"
5354
}
5455
]
56+
},
57+
{
58+
"viewType": "vscode.videoPreview",
59+
"displayName": "%customEditor.videoPreview.displayName%",
60+
"priority": "builtin",
61+
"selector": [
62+
{
63+
"filenamePattern": "*.{mp4}"
64+
}
65+
]
5566
}
5667
],
5768
"commands": [
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
{
22
"displayName": "Image Preview",
33
"description": "Provides VS Code's built-in image preview",
4-
"customEditors.displayName": "Image Preview",
4+
"customEditor.audioPreview.displayName": "Audio Preview",
5+
"customEditor.imagePreview.displayName": "Image Preview",
6+
"customEditor.videoPreview.displayName": "Video Preview",
57
"command.zoomIn": "Zoom in",
68
"command.zoomOut": "Zoom out"
79
}

extensions/image-preview/src/audioPreview.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const localize = nls.loadMessageBundle();
1313

1414
class AudioPreviewProvider implements vscode.CustomReadonlyEditorProvider {
1515

16-
public static readonly viewType = 'vscode.mediaPreview.audioView';
16+
public static readonly viewType = 'vscode.audioPreview';
1717

1818
constructor(
1919
private readonly extensionRoot: vscode.Uri,
@@ -32,8 +32,6 @@ class AudioPreviewProvider implements vscode.CustomReadonlyEditorProvider {
3232

3333
class AudioPreview extends MediaPreview {
3434

35-
private readonly emptyAudioDataUri = 'data:audio/wav;base64,';
36-
3735
constructor(
3836
private readonly extensionRoot: vscode.Uri,
3937
resource: vscode.Uri,
@@ -92,11 +90,12 @@ class AudioPreview extends MediaPreview {
9290
</html>`;
9391
}
9492

95-
private async getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string): Promise<string> {
93+
private async getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string): Promise<string | null> {
9694
if (resource.scheme === 'git') {
9795
const stat = await vscode.workspace.fs.stat(resource);
9896
if (stat.size === 0) {
99-
return this.emptyAudioDataUri;
97+
// The file is stored on git lfs
98+
return null;
10099
}
101100
}
102101

extensions/image-preview/src/extension.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as vscode from 'vscode';
7+
import { registerAudioPreviewSupport } from './audioPreview';
78
import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry';
89
import { registerImagePreviewSupport } from './imagePreview';
9-
import { registerAudioPreviewSupport } from './audioPreview';
10+
import { registerVideoPreviewSupport } from './videoPreview';
1011

1112
export function activate(context: vscode.ExtensionContext) {
1213
const binarySizeStatusBarEntry = new BinarySizeStatusBarEntry();
1314
context.subscriptions.push(binarySizeStatusBarEntry);
1415

1516
context.subscriptions.push(registerImagePreviewSupport(context, binarySizeStatusBarEntry));
1617
context.subscriptions.push(registerAudioPreviewSupport(context, binarySizeStatusBarEntry));
18+
context.subscriptions.push(registerVideoPreviewSupport(context, binarySizeStatusBarEntry));
1719
}

0 commit comments

Comments
 (0)