From 8fa95443aa4e6068caac055902a64c3b479c021f Mon Sep 17 00:00:00 2001 From: Chneemann Date: Tue, 13 Aug 2024 11:13:04 +0200 Subject: [PATCH] =?UTF-8?q?videos=20werden=20in=20.m3u8=20beim=20hochladen?= =?UTF-8?q?=20gerendert,=20videojs=20player=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/videoflix/content/models.py | 4 +- backend/videoflix/content/signals.py | 54 +- backend/videoflix/content/tasks.py | 75 ++- frontend/angular.json | 31 +- frontend/package-lock.json | 509 +++++++++++++++++- frontend/package.json | 3 + .../home/browse/browse.component.html | 10 +- .../home/browse/browse.component.ts | 4 +- .../categories/categories.component.html | 16 +- .../hero-banner/hero-banner.component.html | 21 +- .../vjs-player/vjs-player.component.html | 3 + .../vjs-player/vjs-player.component.scss | 0 .../vjs-player/vjs-player.component.spec.ts | 22 + .../browse/vjs-player/vjs-player.component.ts | 56 ++ 14 files changed, 720 insertions(+), 88 deletions(-) create mode 100644 frontend/src/app/components/home/browse/vjs-player/vjs-player.component.html create mode 100644 frontend/src/app/components/home/browse/vjs-player/vjs-player.component.scss create mode 100644 frontend/src/app/components/home/browse/vjs-player/vjs-player.component.spec.ts create mode 100644 frontend/src/app/components/home/browse/vjs-player/vjs-player.component.ts diff --git a/backend/videoflix/content/models.py b/backend/videoflix/content/models.py index 101f155..1a49592 100644 --- a/backend/videoflix/content/models.py +++ b/backend/videoflix/content/models.py @@ -8,7 +8,7 @@ class Video(models.Model): description = models.CharField(max_length=500) film_genre = models.CharField(max_length=20, choices=FILM_GENRES, blank=True, null=True) video_file = models.FileField(upload_to='videos/', blank=True, null=True) - thumbnail = models.ImageField(upload_to='thumbnails/', blank=True, null=True) + file_name = models.CharField(max_length=50, blank=True, null=True) def __str__(self): - return self.title \ No newline at end of file + return f'({self.id}) {self.title}' \ No newline at end of file diff --git a/backend/videoflix/content/signals.py b/backend/videoflix/content/signals.py index 4eff43d..01a963f 100644 --- a/backend/videoflix/content/signals.py +++ b/backend/videoflix/content/signals.py @@ -1,10 +1,11 @@ from .models import Video -from content.tasks import convert_video, create_thumbnails +from .tasks import convert_video_to_hls, create_thumbnails, delete_original_video from django.dispatch import receiver from django.db.models.signals import post_save, post_delete from django.conf import settings import os import django_rq +import shutil @receiver(post_save, sender=Video) def video_post_save(sender, instance, created, **kwargs): @@ -14,41 +15,36 @@ def video_post_save(sender, instance, created, **kwargs): if created: queue = django_rq.get_queue("default", autocommit=True) - #Convert Video - queue.enqueue(convert_video, instance.video_file.path, "480") - queue.enqueue(convert_video, instance.video_file.path, "720") - queue.enqueue(convert_video, instance.video_file.path, "1080") + #Convert video + queue.enqueue(convert_video_to_hls, instance.video_file.path, "480", instance.id) + queue.enqueue(convert_video_to_hls, instance.video_file.path, "720", instance.id) + queue.enqueue(convert_video_to_hls, instance.video_file.path, "1080", instance.id) - #Create Thumbnail - queue.enqueue(create_thumbnails, instance) + #Create thumbnail + queue.enqueue(create_thumbnails, instance, instance.id) + + # Delete the original video file + queue.enqueue(delete_original_video, instance.video_file.path) @receiver(post_delete, sender=Video) def auto_delete_file_on_delete(sender, instance, **kwargs): """ - Deletes video file and its converted files from filesystem - when corresponding `Video` object is deleted. + Deletes the video and all converted files from the file system, + when the corresponding `Video` object is deleted. """ - # Delete the original video file - if instance.video_file: - if os.path.isfile(instance.video_file.path): - os.remove(instance.video_file.path) - - # Delete the converted videos - if instance.video_file: - delete_converted_files(instance.video_file.path) - - # Delete the thumbnails - if instance.thumbnail: - thumbnail_dir = settings.THUMBNAIL_DIR - base_filename = os.path.splitext(os.path.basename(instance.video_file.path))[0] - thumbnail_1080p_path = os.path.join(thumbnail_dir, base_filename + '_1080p.jpg') - thumbnail_480_path = os.path.join(thumbnail_dir, base_filename + '_480p.jpg') - - if os.path.isfile(thumbnail_1080p_path): - os.remove(thumbnail_1080p_path) - if os.path.isfile(thumbnail_480_path): - os.remove(thumbnail_480_path) + main_directory = os.path.dirname(instance.video_file.path) + model_directory = os.path.join(main_directory, str(instance.id)) + delete_directory(model_directory) +def delete_directory(directory_path): + """ + Deletes the specified directory recursively. + """ + if os.path.exists(directory_path): + shutil.rmtree(directory_path) + else: + print(f"Folder not found: {directory_path}") + def remove_first_mp4(filename): """ Remove the first .mp4 extension from the filename. diff --git a/backend/videoflix/content/tasks.py b/backend/videoflix/content/tasks.py index 139b8a2..c4e652d 100644 --- a/backend/videoflix/content/tasks.py +++ b/backend/videoflix/content/tasks.py @@ -1,31 +1,70 @@ +import re import subprocess import ffmpeg import os from django.conf import settings +from .models import Video -def convert_video(source, resolution): - target = source + f'_{resolution}p.mp4' - target = remove_first_mp4(target) - ffmpeg_path = '/opt/homebrew/bin/ffmpeg' - cmd = [ffmpeg_path, '-i', source, '-s', f'hd{resolution}', '-c:v', 'libx264', '-crf', '23', '-c:a', 'aac', '-strict', '-2', target] - result = subprocess.run(cmd, capture_output=True, text=True) +def convert_video_to_hls(source, resolution, model_id): + target_dir = os.path.join(os.path.dirname(source), str(model_id)) + os.makedirs(target_dir, exist_ok=True) + + base_filename = os.path.basename(source).split(".")[0] + target = os.path.join(target_dir, f'{base_filename}_{resolution}p') -def remove_first_mp4(filename): - name_part, ext_part = filename.split('.mp4', 1) - new_filename = name_part + ext_part - return new_filename - -def create_thumbnails(self): - video_file_path = self.video_file.path - thumbnail_dir = settings.THUMBNAIL_DIR + ffmpeg_path = '/opt/homebrew/bin/ffmpeg' + cmd = [ + ffmpeg_path, + '-i', source, + '-s', f'hd{resolution}', + '-c:v', 'libx264', + '-crf', '23', + '-c:a', 'aac', + '-strict', '-2', + '-hls_time', '10', + '-hls_playlist_type', 'vod', + '-hls_segment_filename', os.path.join(target_dir, f'{base_filename}_{resolution}p_%03d.ts'), + f'{target}.m3u8' + ] + + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode == 0: + save_converted_file_name(model_id, base_filename) + else: + print(f"Conversion error: {result.stderr}") +def save_converted_file_name(model_id, file_name): + """ + Saves the name of the converted file in the model or a database. + """ + try: + instance = Video.objects.get(id=model_id) + instance.file_name = file_name + instance.save() + except Video.DoesNotExist: + print(f"Model with ID {model_id} not found.") + except Exception as e: + print(f"Error saving file name:: {e}") + +def delete_original_video(source): + """ + Deletes the original MP4 file. + """ + try: + os.remove(source) + except OSError as e: + print(f"Error deleting original file: {e}") + +def create_thumbnails(self, model_id): + video_file_path = self.video_file.path + thumbnail_dir = os.path.join(settings.THUMBNAIL_DIR, str(model_id)) + base_filename = os.path.splitext(os.path.basename(video_file_path))[0] thumbnail_1080p_filename = base_filename + '_1080p.jpg' thumbnail_1080p_path = os.path.join(thumbnail_dir, thumbnail_1080p_filename) thumbnail_480_filename = base_filename + '_480p.jpg' thumbnail_480_path = os.path.join(thumbnail_dir, thumbnail_480_filename) - thumbnail_path = os.path.join(thumbnail_dir, base_filename + ".jpg") - + if not os.path.exists(thumbnail_dir): os.makedirs(thumbnail_dir) @@ -33,9 +72,5 @@ def create_thumbnails(self): ffmpeg.input(video_file_path, ss=1).output(thumbnail_1080p_path, vf='scale=1920:-1', vframes=1).run(overwrite_output=True) ffmpeg.input(video_file_path, ss=1).output(thumbnail_480_path, vf='scale=720:-1', vframes=1).run(overwrite_output=True) - # Hier speichern wir nur das Thumbnail für die Vorschau im Modell - self.thumbnail = os.path.relpath(thumbnail_path, start=settings.MEDIA_ROOT) - self.save() - except ffmpeg._run.Error as e: print(f"Ein Fehler ist aufgetreten: {e.stderr.decode()}") \ No newline at end of file diff --git a/frontend/angular.json b/frontend/angular.json index 3af4900..6d346f9 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -20,18 +20,11 @@ "outputPath": "dist/frontend", "index": "src/index.html", "browser": "src/main.ts", - "polyfills": [ - "zone.js" - ], + "polyfills": ["zone.js"], "tsConfig": "tsconfig.app.json", "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/styles.scss" - ], + "assets": ["src/favicon.ico", "src/assets"], + "styles": ["src/styles.scss"], "scripts": [] }, "configurations": { @@ -79,20 +72,18 @@ "test": { "builder": "@angular-devkit/build-angular:karma", "options": { - "polyfills": [ - "zone.js", - "zone.js/testing" - ], + "polyfills": ["zone.js", "zone.js/testing"], "tsConfig": "tsconfig.spec.json", "inlineStyleLanguage": "scss", - "assets": [ - "src/favicon.ico", - "src/assets" - ], + "assets": ["src/favicon.ico", "src/assets"], "styles": [ - "src/styles.scss" + "src/styles.scss", + "node_modules/video.js/dist/video-js.css" ], - "scripts": [] + "scripts": [ + "node_modules/video.js/dist/video.min.js", + "node_modules/videojs-contrib-hls/dist/videojs-contrib-hls.js" + ] } } } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ec4e21b..f643f9b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,6 +18,8 @@ "@angular/router": "^17.2.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", + "video.js": "^8.17.2", + "videojs-contrib-hls": "^5.15.0", "zone.js": "~0.14.3" }, "devDependencies": { @@ -25,6 +27,7 @@ "@angular/cli": "^17.2.1", "@angular/compiler-cli": "^17.2.0", "@types/jasmine": "~5.1.0", + "@types/video.js": "^7.3.58", "jasmine-core": "~5.1.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", @@ -2220,7 +2223,6 @@ "version": "7.24.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -3786,6 +3788,12 @@ "@types/node": "*" } }, + "node_modules/@types/video.js": { + "version": "7.3.58", + "resolved": "https://registry.npmjs.org/@types/video.js/-/video.js-7.3.58.tgz", + "integrity": "sha512-1CQjuSrgbv1/dhmcfQ83eVyYbvGyqhTvb2Opxr0QCV+iJ4J6/J+XWQ3Om59WiwCd1MN3rDUHasx5XRrpUtewYQ==", + "dev": true + }, "node_modules/@types/ws": { "version": "8.5.12", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", @@ -3795,6 +3803,52 @@ "@types/node": "*" } }, + "node_modules/@videojs/http-streaming": { + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.13.2.tgz", + "integrity": "sha512-eCfQp61w00hg7Y9npmLnsJ6UvDF+SsFYzu7mQJgVXxWpVm9AthYWA3KQEKA7F7Sy6yzlm/Sps8BHs5ItelNZgQ==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "4.0.0", + "aes-decrypter": "4.0.1", + "global": "^4.4.0", + "m3u8-parser": "^7.1.0", + "mpd-parser": "^1.3.0", + "mux.js": "7.0.3", + "video.js": "^7 || ^8" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + }, + "peerDependencies": { + "video.js": "^8.14.0" + } + }, + "node_modules/@videojs/vhs-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-4.0.0.tgz", + "integrity": "sha512-xJp7Yd4jMLwje2vHCUmi8MOUU76nxiwII3z4Eg3Ucb+6rrkFVGosrXlMgGnaLjq724j3wzNElRZ71D/CKrTtxg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "global": "^4.4.0", + "url-toolkit": "^2.2.1" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, + "node_modules/@videojs/xhr": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.7.0.tgz", + "integrity": "sha512-giab+EVRanChIupZK7gXjHy90y3nncA2phIOyG3Ne5fvpiMJzvqYwiTOnEVW2S4CoYcuKJkomat7bMXA/UoUZQ==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "global": "~4.4.0", + "is-function": "^1.0.1" + } + }, "node_modules/@vitejs/plugin-basic-ssl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", @@ -3953,6 +4007,14 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -4041,6 +4103,31 @@ "node": ">=8.9.0" } }, + "node_modules/aes-decrypter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-4.0.1.tgz", + "integrity": "sha512-H1nh/P9VZXUf17AA5NQfJML88CFjVBDuGkp5zDHa7oEhYN9TTpNLJknRY1ie0iSKWlDf6JRnJKaZVDSQdPy6Cg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.5", + "global": "^4.4.0", + "pkcs7": "^1.0.4" + } + }, + "node_modules/aes-decrypter/node_modules/@videojs/vhs-utils": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz", + "integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "global": "^4.4.0", + "url-toolkit": "^2.2.1" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, "node_modules/agent-base": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", @@ -4358,6 +4445,20 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -5128,6 +5229,13 @@ "node": ">=10.13.0" } }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true + }, "node_modules/core-js-compat": { "version": "3.37.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", @@ -5556,6 +5664,11 @@ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, "node_modules/domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", @@ -5793,6 +5906,14 @@ "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", "dev": true }, + "node_modules/es5-shim": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.6.7.tgz", + "integrity": "sha512-jg21/dmlrNQI7JyyA2w7n+yifSxBng0ZralnSfVZjoCawgNTCnS+yBCyVM9DL5itm7SUnDGgv7hcq2XCZX4iRQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/esbuild": { "version": "0.20.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz", @@ -6501,6 +6622,15 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -6978,6 +7108,11 @@ "node": ">=8" } }, + "node_modules/individual": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz", + "integrity": "sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g==" + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -7136,6 +7271,11 @@ "node": ">=8" } }, + "node_modules/is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -8040,6 +8180,30 @@ "yallist": "^3.0.2" } }, + "node_modules/m3u8-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-7.1.0.tgz", + "integrity": "sha512-7N+pk79EH4oLKPEYdgRXgAsKDyA/VCo0qCHlUwacttQA0WqsjZQYmNfywMvjlY9MpEBVZEt0jKFd73Kv15EBYQ==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.5", + "global": "^4.4.0" + } + }, + "node_modules/m3u8-parser/node_modules/@videojs/vhs-utils": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz", + "integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "global": "^4.4.0", + "url-toolkit": "^2.2.1" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, "node_modules/magic-string": { "version": "0.30.8", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", @@ -8217,6 +8381,14 @@ "node": ">=6" } }, + "node_modules/min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "dependencies": { + "dom-walk": "^0.1.0" + } + }, "node_modules/mini-css-extract-plugin": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz", @@ -8463,6 +8635,20 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mpd-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.3.0.tgz", + "integrity": "sha512-WgeIwxAqkmb9uTn4ClicXpEQYCEduDqRKfmUdp4X8vmghKfBNXZLYpREn9eqrDx/Tf5LhzRcJLSpi4ohfV742Q==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^4.0.0", + "@xmldom/xmldom": "^0.8.3", + "global": "^4.4.0" + }, + "bin": { + "mpd-to-m3u8-json": "bin/parse.js" + } + }, "node_modules/mrmime": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", @@ -8500,6 +8686,22 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/mux.js": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-7.0.3.tgz", + "integrity": "sha512-gzlzJVEGFYPtl2vvEiJneSWAWD4nfYRHD5XgxmB2gWvXraMPOYk+sxfvexmNfjQUFpmk6hwLR5C6iSFmuwCHdQ==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "global": "^4.4.0" + }, + "bin": { + "muxjs-transmux": "bin/transmux.js" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -9201,6 +9403,11 @@ "node": ">=6" } }, + "node_modules/parse-headers": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -9388,6 +9595,17 @@ "nice-napi": "^1.0.2" } }, + "node_modules/pkcs7": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz", + "integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==", + "dependencies": { + "@babel/runtime": "^7.5.5" + }, + "bin": { + "pkcs7": "bin/cli.js" + } + }, "node_modules/pkg-dir": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", @@ -9625,6 +9843,14 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -9900,8 +10126,7 @@ "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regenerator-transform": { "version": "0.15.2", @@ -10166,6 +10391,14 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rust-result": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz", + "integrity": "sha512-6cJzSBU+J/RJCF063onnQf0cDUOHs9uZI1oroSGnHOph+CQTIJ5Pp2hK5kEQq1+7yE/EEWfulSNXAQ2jikPthA==", + "dependencies": { + "individual": "^2.0.0" + } + }, "node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -10194,6 +10427,14 @@ } ] }, + "node_modules/safe-json-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-4.0.0.tgz", + "integrity": "sha512-RjZPPHugjK0TOzFrLZ8inw44s9bKox99/0AZW9o/BEQVrJfhI+fIHMErnPyRa89/yRXUUr93q+tiN6zhoVV4wQ==", + "dependencies": { + "rust-result": "^1.0.0" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -11233,6 +11474,12 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, + "node_modules/tsml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tsml/-/tsml-1.0.1.tgz", + "integrity": "sha512-3KmepnH9SUsoOVtg013CRrL7c+AK7ECaquAsJdvu4288EDJuraqBlP4PDXT/rLEJ9YDn4jqLAzRJsnFPx+V6lg==", + "deprecated": "no longer maintained" + }, "node_modules/tuf-js": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", @@ -11459,6 +11706,11 @@ "node": ">=6" } }, + "node_modules/url-toolkit": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz", + "integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -11511,6 +11763,216 @@ "node": ">= 0.8" } }, + "node_modules/video.js": { + "version": "8.17.2", + "resolved": "https://registry.npmjs.org/video.js/-/video.js-8.17.2.tgz", + "integrity": "sha512-oa4BGAr5H965OBcn83qM9xMMtjtSCRh0zMLnyouD9itQJ994FY/NlYo+XSPujk4NpsBGHSUF/+rGy0Wu5Mrzqg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/http-streaming": "3.13.2", + "@videojs/vhs-utils": "^4.0.0", + "@videojs/xhr": "2.7.0", + "aes-decrypter": "^4.0.1", + "global": "4.4.0", + "m3u8-parser": "^7.1.0", + "mpd-parser": "^1.2.2", + "mux.js": "^7.0.1", + "safe-json-parse": "4.0.0", + "videojs-contrib-quality-levels": "4.1.0", + "videojs-font": "4.2.0", + "videojs-vtt.js": "0.15.5" + } + }, + "node_modules/videojs-contrib-hls": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/videojs-contrib-hls/-/videojs-contrib-hls-5.15.0.tgz", + "integrity": "sha512-18zbMYZ0XRBKTPEayA9bFTWWrqhT9b4G8+zf0czJLD7Epe5PcK1I/3dflTHQeQ5rwlWir+/XnFU3sMg/B2MMcw==", + "dependencies": { + "aes-decrypter": "1.0.3", + "global": "^4.3.0", + "m3u8-parser": "2.1.0", + "mux.js": "4.3.2", + "url-toolkit": "^2.1.3", + "video.js": "^5.19.1 || ^6.2.0", + "videojs-contrib-media-sources": "4.7.2", + "webwackify": "0.1.6" + }, + "engines": { + "node": ">= 0.10.12" + } + }, + "node_modules/videojs-contrib-hls/node_modules/aes-decrypter": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-1.0.3.tgz", + "integrity": "sha512-rsx8pfx7wJsn+ziYbpJ8XA5c93hKAtBCrfydxJqJCMT+qfjipd/B5wC2xHtBcoxyvlqJcpeAo3K55t0lXOn9yQ==", + "dependencies": { + "pkcs7": "^0.2.3" + } + }, + "node_modules/videojs-contrib-hls/node_modules/global": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha512-/4AybdwIDU4HkCUbJkZdWpe4P6vuw/CUtu+0I1YlLIPe7OlUO7KNJ+q/rO70CW2/NW6Jc6I62++Hzsf5Alu6rQ==", + "dependencies": { + "min-document": "^2.19.0", + "process": "~0.5.1" + } + }, + "node_modules/videojs-contrib-hls/node_modules/m3u8-parser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-2.1.0.tgz", + "integrity": "sha512-WbEpQ2FUaNGbJ0YanSeyj9D9ruu4FUvz+ZvebIzI2bSME+PUwcPXO1kKXZkjcPUAFruDikoOI5fWQNIA6JCCOQ==" + }, + "node_modules/videojs-contrib-hls/node_modules/mux.js": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-4.3.2.tgz", + "integrity": "sha512-g0q6DPdvb3yYcoK7ElBGobdSSrhY/RjPt19U7uUc733aqvc5bCS/aCvL9z+448y+IoCZnYDwyZfQBBXMSmGOaQ==" + }, + "node_modules/videojs-contrib-hls/node_modules/pkcs7": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-0.2.3.tgz", + "integrity": "sha512-kJRwmADEQUg+qJyRgWLtpEL9q9cFjZschejTEK3GRjKvnsU9G5WWoe/wKqRgbBoqWdVSeTUKP6vIA3Y72M3rWA==", + "bin": { + "pkcs7": "lib/cli.js" + }, + "engines": { + "node": "^0.10", + "npm": "^1.4.6" + } + }, + "node_modules/videojs-contrib-hls/node_modules/process": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha512-oNpcutj+nYX2FjdEW7PGltWhXulAnFlM0My/k48L90hARCOJtvBbQXc/6itV2jDvU5xAAtonP+r6wmQgCcbAUA==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/videojs-contrib-hls/node_modules/video.js": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/video.js/-/video.js-6.13.0.tgz", + "integrity": "sha512-36/JR/GhPQSZj0o+GNbhcEYv/b0SkV9SQsjlodAnzMQYN0TA7VhmqrKPYMCi1NGRYu7S9W3OaFCFoUxkYfSVlg==", + "dependencies": { + "babel-runtime": "^6.9.2", + "global": "4.3.2", + "safe-json-parse": "4.0.0", + "tsml": "1.0.1", + "videojs-font": "2.1.0", + "videojs-ie8": "1.1.2", + "videojs-vtt.js": "0.12.6", + "xhr": "2.4.0" + } + }, + "node_modules/videojs-contrib-hls/node_modules/videojs-font": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-2.1.0.tgz", + "integrity": "sha512-zFqWpLrXf1q8NtYx5qtZhMC6SLUFScDmR6j+UGPogobxR21lvXShhnzcNNMdOxJUuFLiToJ/BPpFUQwX4xhpvA==" + }, + "node_modules/videojs-contrib-hls/node_modules/videojs-vtt.js": { + "version": "0.12.6", + "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.12.6.tgz", + "integrity": "sha512-XFXeGBQiljnElMhwCcZst0RDbZn2n8LU7ZScXryd3a00OaZsHAjdZu/7/RdSr7Z1jHphd45FnOvOQkGK4YrWCQ==", + "dependencies": { + "global": "^4.3.1" + } + }, + "node_modules/videojs-contrib-media-sources": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/videojs-contrib-media-sources/-/videojs-contrib-media-sources-4.7.2.tgz", + "integrity": "sha512-e6iCHWBFuV05EGo7v+pS9iepObXnJ9joms467gzi8ZjpKVb3ifha9M0Ja24Rd8JfvYpzjltsgDVtGFDvIg4hQQ==", + "dependencies": { + "global": "^4.3.0", + "mux.js": "4.3.2", + "video.js": "^5.17.0 || ^6.2.0", + "webwackify": "0.1.6" + } + }, + "node_modules/videojs-contrib-media-sources/node_modules/global": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha512-/4AybdwIDU4HkCUbJkZdWpe4P6vuw/CUtu+0I1YlLIPe7OlUO7KNJ+q/rO70CW2/NW6Jc6I62++Hzsf5Alu6rQ==", + "dependencies": { + "min-document": "^2.19.0", + "process": "~0.5.1" + } + }, + "node_modules/videojs-contrib-media-sources/node_modules/mux.js": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-4.3.2.tgz", + "integrity": "sha512-g0q6DPdvb3yYcoK7ElBGobdSSrhY/RjPt19U7uUc733aqvc5bCS/aCvL9z+448y+IoCZnYDwyZfQBBXMSmGOaQ==" + }, + "node_modules/videojs-contrib-media-sources/node_modules/process": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha512-oNpcutj+nYX2FjdEW7PGltWhXulAnFlM0My/k48L90hARCOJtvBbQXc/6itV2jDvU5xAAtonP+r6wmQgCcbAUA==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/videojs-contrib-media-sources/node_modules/video.js": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/video.js/-/video.js-6.13.0.tgz", + "integrity": "sha512-36/JR/GhPQSZj0o+GNbhcEYv/b0SkV9SQsjlodAnzMQYN0TA7VhmqrKPYMCi1NGRYu7S9W3OaFCFoUxkYfSVlg==", + "dependencies": { + "babel-runtime": "^6.9.2", + "global": "4.3.2", + "safe-json-parse": "4.0.0", + "tsml": "1.0.1", + "videojs-font": "2.1.0", + "videojs-ie8": "1.1.2", + "videojs-vtt.js": "0.12.6", + "xhr": "2.4.0" + } + }, + "node_modules/videojs-contrib-media-sources/node_modules/videojs-font": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-2.1.0.tgz", + "integrity": "sha512-zFqWpLrXf1q8NtYx5qtZhMC6SLUFScDmR6j+UGPogobxR21lvXShhnzcNNMdOxJUuFLiToJ/BPpFUQwX4xhpvA==" + }, + "node_modules/videojs-contrib-media-sources/node_modules/videojs-vtt.js": { + "version": "0.12.6", + "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.12.6.tgz", + "integrity": "sha512-XFXeGBQiljnElMhwCcZst0RDbZn2n8LU7ZScXryd3a00OaZsHAjdZu/7/RdSr7Z1jHphd45FnOvOQkGK4YrWCQ==", + "dependencies": { + "global": "^4.3.1" + } + }, + "node_modules/videojs-contrib-quality-levels": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-4.1.0.tgz", + "integrity": "sha512-TfrXJJg1Bv4t6TOCMEVMwF/CoS8iENYsWNKip8zfhB5kTcegiFYezEA0eHAJPU64ZC8NQbxQgOwAsYU8VXbOWA==", + "dependencies": { + "global": "^4.4.0" + }, + "engines": { + "node": ">=16", + "npm": ">=8" + }, + "peerDependencies": { + "video.js": "^8" + } + }, + "node_modules/videojs-font": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-4.2.0.tgz", + "integrity": "sha512-YPq+wiKoGy2/M7ccjmlvwi58z2xsykkkfNMyIg4xb7EZQQNwB71hcSsB3o75CqQV7/y5lXkXhI/rsGAS7jfEmQ==" + }, + "node_modules/videojs-ie8": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/videojs-ie8/-/videojs-ie8-1.1.2.tgz", + "integrity": "sha512-0Zb2T4MLkpfZbeGMK/Z93b8Lrepr+rLFoHgQV1CoDeFqXvH7b+Vsd/VHoILGxQrgCSHFQ7mAODR6oyMjuiD4/g==", + "dependencies": { + "es5-shim": "^4.5.1" + } + }, + "node_modules/videojs-vtt.js": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.5.tgz", + "integrity": "sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==", + "dependencies": { + "global": "^4.3.1" + } + }, "node_modules/vite": { "version": "5.1.7", "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.7.tgz", @@ -12291,6 +12753,11 @@ "node": ">=0.8.0" } }, + "node_modules/webwackify": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/webwackify/-/webwackify-0.1.6.tgz", + "integrity": "sha512-pGcw1T3HpNnM/UTRQqqRkkkzythSLts05mB+7Gr00B+0VbL0m39dFL5g20rSIEUt9Wrpw+/8k+snxRlUFHhcqA==" + }, "node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -12434,6 +12901,42 @@ } } }, + "node_modules/xhr": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.4.0.tgz", + "integrity": "sha512-TUbBsdAuJbX8olk9hsDwGK8P1ri1XlV+PdEWkYw+HQQbpkiBR8PLgD1F3kQDPBs9l4Px34hP9rCYAZOCCAENbw==", + "dependencies": { + "global": "~4.3.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/xhr/node_modules/global": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha512-/4AybdwIDU4HkCUbJkZdWpe4P6vuw/CUtu+0I1YlLIPe7OlUO7KNJ+q/rO70CW2/NW6Jc6I62++Hzsf5Alu6rQ==", + "dependencies": { + "min-document": "^2.19.0", + "process": "~0.5.1" + } + }, + "node_modules/xhr/node_modules/process": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha512-oNpcutj+nYX2FjdEW7PGltWhXulAnFlM0My/k48L90hARCOJtvBbQXc/6itV2jDvU5xAAtonP+r6wmQgCcbAUA==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7e2b1bc..d202ac7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,6 +20,8 @@ "@angular/router": "^17.2.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", + "video.js": "^8.17.2", + "videojs-contrib-hls": "^5.15.0", "zone.js": "~0.14.3" }, "devDependencies": { @@ -27,6 +29,7 @@ "@angular/cli": "^17.2.1", "@angular/compiler-cli": "^17.2.0", "@types/jasmine": "~5.1.0", + "@types/video.js": "^7.3.58", "jasmine-core": "~5.1.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", diff --git a/frontend/src/app/components/home/browse/browse.component.html b/frontend/src/app/components/home/browse/browse.component.html index f1fafa0..0326a96 100644 --- a/frontend/src/app/components/home/browse/browse.component.html +++ b/frontend/src/app/components/home/browse/browse.component.html @@ -20,13 +20,11 @@ Logo -