Skip to content

Commit 290931a

Browse files
authored
Update video uploads to use livepeer client (#629)
1 parent a1abe32 commit 290931a

10 files changed

+98
-84
lines changed

Caddyfile

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ header @html Cache-Control no-store
2828
header @html Content-Security-Policy "
2929
default-src 'self';
3030
connect-src 'self'
31+
media.deso.org
3132
node.deso.org
3233
amp.deso.org
3334
bithunt.deso.org
@@ -48,6 +49,7 @@ header @html Content-Security-Policy "
4849
api.testwyre.com
4950
api.sendwyre.com
5051
https://videodelivery.net
52+
https://lvpr.tv
5153
https://upload.videodelivery.net;
5254
script-src 'self'
5355
https://kit.fontawesome.com/070ca4195b.js
@@ -59,6 +61,7 @@ header @html Content-Security-Policy "
5961
img-src 'self'
6062
data:
6163
i.imgur.com
64+
media.deso.org
6265
images.deso.org
6366
images.bitclout.com
6467
quickchart.io
@@ -97,5 +100,6 @@ header @html Content-Security-Policy "
97100
https://mousai.stream
98101
pay.testwyre.com
99102
pay.sendwyre.com
103+
https://lvpr.tv
100104
https://iframe.videodelivery.net;
101105
frame-ancestors 'self';"

src/app/backend-api.service.ts

+30
Original file line numberDiff line numberDiff line change
@@ -1288,6 +1288,36 @@ export class BackendApiService {
12881288
);
12891289
}
12901290

1291+
UploadVideo(
1292+
endpoint: string,
1293+
file: File,
1294+
publicKeyBase58Check: string
1295+
): Observable<{
1296+
tusEndpoint: string;
1297+
asset: {
1298+
id: string;
1299+
playbackId: string;
1300+
};
1301+
}> {
1302+
const request = this.identityService.jwt({
1303+
...this.identityService.identityServiceParamsForKey(publicKeyBase58Check),
1304+
});
1305+
return request.pipe(
1306+
switchMap((signed) => {
1307+
const formData = new FormData();
1308+
formData.append('file', file);
1309+
formData.append('UserPublicKeyBase58Check', publicKeyBase58Check);
1310+
formData.append('JWT', signed.jwt);
1311+
1312+
return this.post(
1313+
endpoint,
1314+
BackendRoutes.RoutePathUploadVideo,
1315+
formData
1316+
);
1317+
})
1318+
);
1319+
}
1320+
12911321
CreateNft(
12921322
endpoint: string,
12931323
UpdaterPublicKeyBase58Check: string,

src/app/feed/feed-create-post/feed-create-post.component.ts

+37-57
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export class FeedCreatePostComponent implements OnInit {
6969
postImageSrc = null;
7070

7171
postVideoSrc = null;
72+
assetId = '';
7273
videoUploadPercentage = null;
7374

7475
showEmbedURL = false;
@@ -325,64 +326,36 @@ export class FeedCreatePostComponent implements OnInit {
325326
);
326327
}
327328

328-
uploadVideo(file: File): void {
329-
if (file.size > 4 * (1024 * 1024 * 1024)) {
329+
async uploadVideo(file: File): Promise<void> {
330+
if (file.size > 65 * 1024 * 1024) {
330331
this.globalVars._alertError(
331-
'File is too large. Please choose a file less than 4GB'
332+
'File is too large. Please choose a file less than 65MB'
332333
);
333334
return;
334335
}
335-
let upload: tus.Upload;
336-
let mediaId = '';
337-
const comp: FeedCreatePostComponent = this;
338-
const options = {
339-
endpoint: this.backendApi._makeRequestURL(
340-
environment.uploadVideoHostname,
341-
BackendRoutes.RoutePathUploadVideo
342-
),
343-
chunkSize: 50 * 1024 * 1024, // Required a minimum chunk size of 5MB, here we use 50MB.
344-
uploadSize: file.size,
345-
onError: function (error) {
346-
comp.globalVars._alertError(error.message);
347-
upload.abort(true).then(() => {
348-
throw error;
349-
});
350-
},
351-
onProgress: function (bytesUploaded, bytesTotal) {
352-
comp.videoUploadPercentage = (
353-
(bytesUploaded / bytesTotal) *
354-
100
355-
).toFixed(2);
356-
},
357-
onSuccess: function () {
358-
// Construct the url for the video based on the videoId and use the iframe url.
359-
comp.postVideoSrc = `https://iframe.videodelivery.net/${mediaId}`;
360-
comp.postImageSrc = null;
361-
comp.videoUploadPercentage = null;
362-
comp.pollForReadyToStream();
363-
},
364-
onAfterResponse: function (req, res) {
365-
return new Promise((resolve) => {
366-
// The stream-media-id header is the video Id in Cloudflare's system that we'll need to locate the video for streaming.
367-
let mediaIdHeader = res.getHeader('stream-media-id');
368-
if (mediaIdHeader) {
369-
mediaId = mediaIdHeader;
370-
}
371-
resolve(res);
372-
});
373-
},
374-
};
375-
// Clear the interval used for polling cloudflare to check if a video is ready to stream.
376-
if (this.videoStreamInterval != null) {
377-
clearInterval(this.videoStreamInterval);
336+
// Set this so that the video upload progress bar shows up.
337+
this.postVideoSrc = 'https://lvpr.tv';
338+
let tusEndpoint, asset;
339+
try {
340+
({ tusEndpoint, asset } = await this.backendApi
341+
.UploadVideo(
342+
environment.uploadVideoHostname,
343+
file,
344+
this.globalVars.loggedInUser.PublicKeyBase58Check
345+
)
346+
.toPromise());
347+
} catch (e) {
348+
this.postVideoSrc = '';
349+
this.globalVars._alertError(JSON.stringify(e.error.error));
350+
return;
378351
}
379-
// Reset the postVideoSrc and readyToStream values.
380-
this.postVideoSrc = null;
381-
this.readyToStream = false;
382-
// Create and start the upload.
383-
upload = new tus.Upload(file, options);
384-
upload.start();
385-
return;
352+
353+
this.postVideoSrc = `https://lvpr.tv/?v=${asset.playbackId}`;
354+
this.assetId = asset.id;
355+
this.postImageSrc = '';
356+
this.videoUploadPercentage = null;
357+
358+
this.pollForReadyToStream();
386359
}
387360

388361
pollForReadyToStream(): void {
@@ -395,19 +368,26 @@ export class FeedCreatePostComponent implements OnInit {
395368
return;
396369
}
397370
this.streamService
398-
.checkVideoStatusByURL(this.postVideoSrc)
399-
.subscribe(([readyToStream, exitPolling]) => {
371+
.checkVideoStatusByURL(this.assetId)
372+
.then(([readyToStream, exitPolling, failed]) => {
400373
if (readyToStream) {
401374
this.readyToStream = true;
402375
clearInterval(this.videoStreamInterval);
403376
return;
404377
}
378+
if (failed) {
379+
clearInterval(this.videoStreamInterval);
380+
this.postVideoSrc = '';
381+
this.globalVars._alertError(
382+
'Video failed to upload. Please try again.'
383+
);
384+
}
405385
if (exitPolling) {
406386
clearInterval(this.videoStreamInterval);
407387
return;
408388
}
409-
})
410-
.add(() => attempts++);
389+
});
390+
attempts++;
411391
}, timeoutMillis);
412392
}
413393
}

src/environments/environment.bitclout.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export const environment = {
22
production: true,
33
uploadImageHostname: 'node.deso.org',
44
verificationEndpointHostname: 'https://node.deso.org',
5-
uploadVideoHostname: 'node.deso.org',
5+
uploadVideoHostname: 'media.deso.org',
66
identityURL: 'https://identity.deso.org',
77
supportEmail: '[email protected]',
88
dd: {

src/environments/environment.deso.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export const environment = {
22
production: true,
33
uploadImageHostname: 'node.deso.org',
44
verificationEndpointHostname: 'https://node.deso.org',
5-
uploadVideoHostname: 'node.deso.org',
5+
uploadVideoHostname: 'media.deso.org',
66
identityURL: 'https://identity.deso.org',
77
supportEmail: '',
88
dd: {

src/environments/environment.dev.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export const environment = {
22
production: true,
33
uploadImageHostname: 'test.deso.org',
44
verificationEndpointHostname: 'https://test.deso.org',
5-
uploadVideoHostname: 'test.deso.org',
5+
uploadVideoHostname: 'media.deso.org',
66
identityURL: 'https://identity.deso.org',
77
supportEmail: '',
88
dd: {

src/environments/environment.prod.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export const environment = {
22
production: true,
33
uploadImageHostname: 'node.deso.org',
44
verificationEndpointHostname: 'https://node.deso.org',
5-
uploadVideoHostname: 'node.deso.org',
5+
uploadVideoHostname: 'media.deso.org',
66
identityURL: 'https://identity.deso.org',
77
supportEmail: '',
88
dd: {

src/environments/environment.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const environment = {
66
production: false,
77
uploadImageHostname: 'node.deso.org',
88
verificationEndpointHostname: 'https://node.deso.org',
9-
uploadVideoHostname: 'https://node.deso.org',
9+
uploadVideoHostname: 'https://media.deso.org',
1010
identityURL: 'https://identity.deso.org',
1111
supportEmail: '',
1212
dd: {

src/lib/pipes/sanitize-video-url-pipe.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export class SanitizeVideoUrlPipe implements PipeTransform {
1414
return false;
1515
}
1616
// On this node, we also validate that it matches the expect video URL format
17-
const regExp = /^https:\/\/iframe\.videodelivery\.net\/[A-Za-z0-9]+$/;
17+
const regExp = /^https:\/\/lvpr.tv\/\?v=[A-Za-z0-9]+$|^https:\/\/iframe\.videodelivery\.net\/[A-Za-z0-9]+[A-Za-z0-9]+(\?([A-Za-z0-9]+\=[A-Za-z0-9]+\&?)*)?$/;
1818
const match = videoURL.match(regExp);
1919
return (
2020
match && match[0] && this.sanitizer.bypassSecurityTrustResourceUrl(url)

src/lib/services/stream/cloudflare-stream-service.ts

+21-21
Original file line numberDiff line numberDiff line change
@@ -23,33 +23,33 @@ export class CloudflareStreamService {
2323
}
2424

2525
// Returns two booleans - the first indicates if a video is ready to stream, the second indicates if we should stop polling
26-
checkVideoStatusByVideoID(videoID: string): Observable<[boolean, boolean]> {
27-
if (videoID === '') {
26+
checkVideoStatusByVideoID(
27+
assetId: string
28+
): Promise<[boolean, boolean, boolean]> {
29+
if (assetId === '') {
2830
console.error('invalid VideoID');
29-
return of([false, true]);
31+
return Promise.resolve([false, true, true]);
3032
}
3133
return this.backendApi
32-
.GetVideoStatus(environment.uploadVideoHostname, videoID)
33-
.pipe(
34-
catchError((error) => {
35-
console.error(error);
36-
return of({
37-
ReadyToStream: false,
38-
Error: error,
39-
});
40-
}),
41-
map((res) => {
42-
return [res.ReadyToStream, res.Error || res.ReadyToStream];
43-
})
44-
);
34+
.GetVideoStatus(environment.uploadVideoHostname, assetId)
35+
.toPromise()
36+
.then(({ status }) => {
37+
const phase = status?.phase;
38+
if (phase === 'ready') {
39+
return [true, true, false];
40+
} else if (phase === 'failed') {
41+
return [false, true, true];
42+
} else {
43+
return [false, false, false];
44+
}
45+
});
4546
}
4647

47-
checkVideoStatusByURL(videoURL: string): Observable<[boolean, boolean]> {
48-
const videoID = this.extractVideoID(videoURL);
49-
if (videoID == '') {
48+
checkVideoStatusByURL(assetId: string): Promise<[boolean, boolean, boolean]> {
49+
if (assetId == '') {
5050
console.error('unable to extract VideoID');
51-
return of([false, true]);
51+
return Promise.resolve([false, true, true]);
5252
}
53-
return this.checkVideoStatusByVideoID(this.extractVideoID(videoURL));
53+
return this.checkVideoStatusByVideoID(assetId);
5454
}
5555
}

0 commit comments

Comments
 (0)