Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update video uploads to use livepeer client #629

Merged
merged 3 commits into from
Feb 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ header @html Cache-Control no-store
header @html Content-Security-Policy "
default-src 'self';
connect-src 'self'
media.deso.org
node.deso.org
amp.deso.org
bithunt.deso.org
Expand All @@ -48,6 +49,7 @@ header @html Content-Security-Policy "
api.testwyre.com
api.sendwyre.com
https://videodelivery.net
https://lvpr.tv
https://upload.videodelivery.net;
script-src 'self'
https://kit.fontawesome.com/070ca4195b.js
Expand All @@ -59,6 +61,7 @@ header @html Content-Security-Policy "
img-src 'self'
data:
i.imgur.com
media.deso.org
images.deso.org
images.bitclout.com
quickchart.io
Expand Down Expand Up @@ -97,5 +100,6 @@ header @html Content-Security-Policy "
https://mousai.stream
pay.testwyre.com
pay.sendwyre.com
https://lvpr.tv
https://iframe.videodelivery.net;
frame-ancestors 'self';"
30 changes: 30 additions & 0 deletions src/app/backend-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,36 @@ export class BackendApiService {
);
}

UploadVideo(
endpoint: string,
file: File,
publicKeyBase58Check: string
): Observable<{
tusEndpoint: string;
asset: {
id: string;
playbackId: string;
};
}> {
const request = this.identityService.jwt({
...this.identityService.identityServiceParamsForKey(publicKeyBase58Check),
});
return request.pipe(
switchMap((signed) => {
const formData = new FormData();
formData.append('file', file);
formData.append('UserPublicKeyBase58Check', publicKeyBase58Check);
formData.append('JWT', signed.jwt);

return this.post(
endpoint,
BackendRoutes.RoutePathUploadVideo,
formData
);
})
);
}

CreateNft(
endpoint: string,
UpdaterPublicKeyBase58Check: string,
Expand Down
94 changes: 37 additions & 57 deletions src/app/feed/feed-create-post/feed-create-post.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export class FeedCreatePostComponent implements OnInit {
postImageSrc = null;

postVideoSrc = null;
assetId = '';
videoUploadPercentage = null;

showEmbedURL = false;
Expand Down Expand Up @@ -325,64 +326,36 @@ export class FeedCreatePostComponent implements OnInit {
);
}

uploadVideo(file: File): void {
if (file.size > 4 * (1024 * 1024 * 1024)) {
async uploadVideo(file: File): Promise<void> {
if (file.size > 65 * 1024 * 1024) {
this.globalVars._alertError(
'File is too large. Please choose a file less than 4GB'
'File is too large. Please choose a file less than 65MB'
);
return;
}
let upload: tus.Upload;
let mediaId = '';
const comp: FeedCreatePostComponent = this;
const options = {
endpoint: this.backendApi._makeRequestURL(
environment.uploadVideoHostname,
BackendRoutes.RoutePathUploadVideo
),
chunkSize: 50 * 1024 * 1024, // Required a minimum chunk size of 5MB, here we use 50MB.
uploadSize: file.size,
onError: function (error) {
comp.globalVars._alertError(error.message);
upload.abort(true).then(() => {
throw error;
});
},
onProgress: function (bytesUploaded, bytesTotal) {
comp.videoUploadPercentage = (
(bytesUploaded / bytesTotal) *
100
).toFixed(2);
},
onSuccess: function () {
// Construct the url for the video based on the videoId and use the iframe url.
comp.postVideoSrc = `https://iframe.videodelivery.net/${mediaId}`;
comp.postImageSrc = null;
comp.videoUploadPercentage = null;
comp.pollForReadyToStream();
},
onAfterResponse: function (req, res) {
return new Promise((resolve) => {
// The stream-media-id header is the video Id in Cloudflare's system that we'll need to locate the video for streaming.
let mediaIdHeader = res.getHeader('stream-media-id');
if (mediaIdHeader) {
mediaId = mediaIdHeader;
}
resolve(res);
});
},
};
// Clear the interval used for polling cloudflare to check if a video is ready to stream.
if (this.videoStreamInterval != null) {
clearInterval(this.videoStreamInterval);
// Set this so that the video upload progress bar shows up.
this.postVideoSrc = 'https://lvpr.tv';
let tusEndpoint, asset;
try {
({ tusEndpoint, asset } = await this.backendApi
.UploadVideo(
environment.uploadVideoHostname,
file,
this.globalVars.loggedInUser.PublicKeyBase58Check
)
.toPromise());
} catch (e) {
this.postVideoSrc = '';
this.globalVars._alertError(JSON.stringify(e.error.error));
return;
}
// Reset the postVideoSrc and readyToStream values.
this.postVideoSrc = null;
this.readyToStream = false;
// Create and start the upload.
upload = new tus.Upload(file, options);
upload.start();
return;

this.postVideoSrc = `https://lvpr.tv/?v=${asset.playbackId}`;
this.assetId = asset.id;
this.postImageSrc = '';
this.videoUploadPercentage = null;

this.pollForReadyToStream();
}

pollForReadyToStream(): void {
Expand All @@ -395,19 +368,26 @@ export class FeedCreatePostComponent implements OnInit {
return;
}
this.streamService
.checkVideoStatusByURL(this.postVideoSrc)
.subscribe(([readyToStream, exitPolling]) => {
.checkVideoStatusByURL(this.assetId)
.then(([readyToStream, exitPolling, failed]) => {
if (readyToStream) {
this.readyToStream = true;
clearInterval(this.videoStreamInterval);
return;
}
if (failed) {
clearInterval(this.videoStreamInterval);
this.postVideoSrc = '';
this.globalVars._alertError(
'Video failed to upload. Please try again.'
);
}
if (exitPolling) {
clearInterval(this.videoStreamInterval);
return;
}
})
.add(() => attempts++);
});
attempts++;
}, timeoutMillis);
}
}
2 changes: 1 addition & 1 deletion src/environments/environment.bitclout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export const environment = {
production: true,
uploadImageHostname: 'node.deso.org',
verificationEndpointHostname: 'https://node.deso.org',
uploadVideoHostname: 'node.deso.org',
uploadVideoHostname: 'media.deso.org',
identityURL: 'https://identity.deso.org',
supportEmail: '[email protected]',
dd: {
Expand Down
2 changes: 1 addition & 1 deletion src/environments/environment.deso.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export const environment = {
production: true,
uploadImageHostname: 'node.deso.org',
verificationEndpointHostname: 'https://node.deso.org',
uploadVideoHostname: 'node.deso.org',
uploadVideoHostname: 'media.deso.org',
identityURL: 'https://identity.deso.org',
supportEmail: '',
dd: {
Expand Down
2 changes: 1 addition & 1 deletion src/environments/environment.dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export const environment = {
production: true,
uploadImageHostname: 'test.deso.org',
verificationEndpointHostname: 'https://test.deso.org',
uploadVideoHostname: 'test.deso.org',
uploadVideoHostname: 'media.deso.org',
identityURL: 'https://identity.deso.org',
supportEmail: '',
dd: {
Expand Down
2 changes: 1 addition & 1 deletion src/environments/environment.prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export const environment = {
production: true,
uploadImageHostname: 'node.deso.org',
verificationEndpointHostname: 'https://node.deso.org',
uploadVideoHostname: 'node.deso.org',
uploadVideoHostname: 'media.deso.org',
identityURL: 'https://identity.deso.org',
supportEmail: '',
dd: {
Expand Down
2 changes: 1 addition & 1 deletion src/environments/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const environment = {
production: false,
uploadImageHostname: 'node.deso.org',
verificationEndpointHostname: 'https://node.deso.org',
uploadVideoHostname: 'https://node.deso.org',
uploadVideoHostname: 'https://media.deso.org',
identityURL: 'https://identity.deso.org',
supportEmail: '',
dd: {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/pipes/sanitize-video-url-pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class SanitizeVideoUrlPipe implements PipeTransform {
return false;
}
// On this node, we also validate that it matches the expect video URL format
const regExp = /^https:\/\/iframe\.videodelivery\.net\/[A-Za-z0-9]+$/;
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]+\&?)*)?$/;
const match = videoURL.match(regExp);
return (
match && match[0] && this.sanitizer.bypassSecurityTrustResourceUrl(url)
Expand Down
42 changes: 21 additions & 21 deletions src/lib/services/stream/cloudflare-stream-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,33 @@ export class CloudflareStreamService {
}

// Returns two booleans - the first indicates if a video is ready to stream, the second indicates if we should stop polling
checkVideoStatusByVideoID(videoID: string): Observable<[boolean, boolean]> {
if (videoID === '') {
checkVideoStatusByVideoID(
assetId: string
): Promise<[boolean, boolean, boolean]> {
if (assetId === '') {
console.error('invalid VideoID');
return of([false, true]);
return Promise.resolve([false, true, true]);
}
return this.backendApi
.GetVideoStatus(environment.uploadVideoHostname, videoID)
.pipe(
catchError((error) => {
console.error(error);
return of({
ReadyToStream: false,
Error: error,
});
}),
map((res) => {
return [res.ReadyToStream, res.Error || res.ReadyToStream];
})
);
.GetVideoStatus(environment.uploadVideoHostname, assetId)
.toPromise()
.then(({ status }) => {
const phase = status?.phase;
if (phase === 'ready') {
return [true, true, false];
} else if (phase === 'failed') {
return [false, true, true];
} else {
return [false, false, false];
}
});
}

checkVideoStatusByURL(videoURL: string): Observable<[boolean, boolean]> {
const videoID = this.extractVideoID(videoURL);
if (videoID == '') {
checkVideoStatusByURL(assetId: string): Promise<[boolean, boolean, boolean]> {
if (assetId == '') {
console.error('unable to extract VideoID');
return of([false, true]);
return Promise.resolve([false, true, true]);
}
return this.checkVideoStatusByVideoID(this.extractVideoID(videoURL));
return this.checkVideoStatusByVideoID(assetId);
}
}