diff --git a/backend/videoflix/content/admin.py b/backend/videoflix/content/admin.py index 63b258b..31702ee 100644 --- a/backend/videoflix/content/admin.py +++ b/backend/videoflix/content/admin.py @@ -6,7 +6,9 @@ from import_export.admin import ImportExportModelAdmin class VideoResource(resources.ModelResource): class Meta: model = Video - + @admin.register(Video) class VideoAdmin(ImportExportModelAdmin): - pass \ No newline at end of file + resource_class = VideoResource + readonly_fields = ('file_name',) + list_display = ('title', 'file_name', 'created_at') \ No newline at end of file diff --git a/backend/videoflix/content/class_assets.py b/backend/videoflix/content/class_assets.py index 2d707cc..c06ad1e 100644 --- a/backend/videoflix/content/class_assets.py +++ b/backend/videoflix/content/class_assets.py @@ -15,4 +15,5 @@ FILM_GENRES = [ ('musical', 'Musical'), ('war', 'War'), ('western', 'Western'), + ('other', 'Miscellaneous'), ] diff --git a/backend/videoflix/content/models.py b/backend/videoflix/content/models.py index 1a49592..a522d1f 100644 --- a/backend/videoflix/content/models.py +++ b/backend/videoflix/content/models.py @@ -1,7 +1,7 @@ from django.db import models from datetime import date from .class_assets import FILM_GENRES - +import os class Video(models.Model): created_at = models.DateField(default=date.today) title = models.CharField(max_length=80) @@ -11,4 +11,12 @@ class Video(models.Model): file_name = models.CharField(max_length=50, blank=True, null=True) def __str__(self): - return f'({self.id}) {self.title}' \ No newline at end of file + return f'({self.id}) {self.title}' + + def save(self, *args, **kwargs): + if self.video_file and not self.file_name: + video_file_path = self.video_file.path + base_filename, _ = os.path.splitext(os.path.basename(video_file_path)) + self.file_name = base_filename + super().save(*args, **kwargs) + \ No newline at end of file diff --git a/backend/videoflix/content/signals.py b/backend/videoflix/content/signals.py index 01a963f..07c142b 100644 --- a/backend/videoflix/content/signals.py +++ b/backend/videoflix/content/signals.py @@ -15,14 +15,14 @@ def video_post_save(sender, instance, created, **kwargs): if created: queue = django_rq.get_queue("default", autocommit=True) + #Create thumbnail + queue.enqueue(create_thumbnails, instance, instance.id) + #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, instance.id) - # Delete the original video file queue.enqueue(delete_original_video, instance.video_file.path) @@ -33,8 +33,12 @@ def auto_delete_file_on_delete(sender, instance, **kwargs): when the corresponding `Video` object is deleted. """ main_directory = os.path.dirname(instance.video_file.path) + parent_directory = os.path.dirname(main_directory) model_directory = os.path.join(main_directory, str(instance.id)) + thumbnail_directory = os.path.join(parent_directory, 'thumbnails', str(instance.id)) + delete_directory(model_directory) + delete_directory(thumbnail_directory) def delete_directory(directory_path): """ diff --git a/backend/videoflix/content/tasks.py b/backend/videoflix/content/tasks.py index c4e652d..b593eff 100644 --- a/backend/videoflix/content/tasks.py +++ b/backend/videoflix/content/tasks.py @@ -27,25 +27,8 @@ def convert_video_to_hls(source, resolution, model_id): 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}") + subprocess.run(cmd, capture_output=True, text=True) -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. diff --git a/backend/videoflix/content/views.py b/backend/videoflix/content/views.py index e5ebf7a..eb132ca 100644 --- a/backend/videoflix/content/views.py +++ b/backend/videoflix/content/views.py @@ -7,16 +7,28 @@ from .models import Video from django.core.cache.backends.base import DEFAULT_TIMEOUT from django.views.decorators.cache import cache_page from django.conf import settings +from rest_framework.parsers import MultiPartParser, FormParser CACHETTL = getattr(settings, 'CACHE_TTL', DEFAULT_TIMEOUT) # Create your views here. @api_view(['GET']) @permission_classes([IsAuthenticated]) -@cache_page(CACHETTL) +#@cache_page(CACHETTL) def video_list(request): if request.method == 'GET': video = Video.objects.all() serializer = VideoSerializer(video, many=True) - return Response(serializer.data) \ No newline at end of file + return Response(serializer.data) + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def video_upload(request): + parser_classes = (MultiPartParser, FormParser) + if request.method == 'POST': + serializer = VideoSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=201) + return Response({"error": "Invalid data", "details": serializer.errors}, status=400) \ No newline at end of file diff --git a/backend/videoflix/videoflix/urls.py b/backend/videoflix/videoflix/urls.py index 0d5040b..2376382 100644 --- a/backend/videoflix/videoflix/urls.py +++ b/backend/videoflix/videoflix/urls.py @@ -36,6 +36,7 @@ urlpatterns = [ # Content URLs path('content/', content_views.video_list), + path('content/upload/', content_views.video_upload), # Users URLs path('users/', user_views.user_list), diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f643f9b..ee84646 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,10 +16,9 @@ "@angular/platform-browser": "^17.2.0", "@angular/platform-browser-dynamic": "^17.2.0", "@angular/router": "^17.2.0", + "hls.js": "^1.5.14", "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,7 +26,6 @@ "@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", @@ -2223,6 +2221,7 @@ "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" }, @@ -3788,12 +3787,6 @@ "@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", @@ -3803,52 +3796,6 @@ "@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", @@ -4007,14 +3954,6 @@ "@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", @@ -4103,31 +4042,6 @@ "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", @@ -4445,20 +4359,6 @@ "@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", @@ -5229,13 +5129,6 @@ "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", @@ -5664,11 +5557,6 @@ "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", @@ -5906,14 +5794,6 @@ "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", @@ -6622,15 +6502,6 @@ "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", @@ -6740,6 +6611,11 @@ "node": ">= 0.4" } }, + "node_modules/hls.js": { + "version": "1.5.14", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.5.14.tgz", + "integrity": "sha512-5wLiQ2kWJMui6oUslaq8PnPOv1vjuee5gTxjJD0DSsccY12OXtDT0h137UuqjczNeHzeEYR0ROZQibKNMr7Mzg==" + }, "node_modules/hosted-git-info": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", @@ -7108,11 +6984,6 @@ "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", @@ -7271,11 +7142,6 @@ "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", @@ -8180,30 +8046,6 @@ "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", @@ -8381,14 +8223,6 @@ "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", @@ -8635,20 +8469,6 @@ "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", @@ -8686,22 +8506,6 @@ "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", @@ -9403,11 +9207,6 @@ "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", @@ -9595,17 +9394,6 @@ "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", @@ -9843,14 +9631,6 @@ "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", @@ -10126,7 +9906,8 @@ "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==" + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true }, "node_modules/regenerator-transform": { "version": "0.15.2", @@ -10391,14 +10172,6 @@ "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", @@ -10427,14 +10200,6 @@ } ] }, - "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", @@ -11474,12 +11239,6 @@ "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", @@ -11706,11 +11465,6 @@ "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", @@ -11763,216 +11517,6 @@ "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", @@ -12753,11 +12297,6 @@ "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", @@ -12901,42 +12440,6 @@ } } }, - "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 d202ac7..159ab35 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,10 +18,9 @@ "@angular/platform-browser": "^17.2.0", "@angular/platform-browser-dynamic": "^17.2.0", "@angular/router": "^17.2.0", + "hls.js": "^1.5.14", "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": { @@ -29,7 +28,6 @@ "@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 13b6a5c..d6d0791 100644 --- a/frontend/src/app/components/home/browse/browse.component.html +++ b/frontend/src/app/components/home/browse/browse.component.html @@ -1,5 +1,5 @@
- @if (currentMovie.length < 0) { + @if (currentMovie[0]) { @if (playMovie === "") { @@ -18,6 +18,38 @@
Back
+
+ + + +
diff --git a/frontend/src/app/components/home/browse/browse.component.scss b/frontend/src/app/components/home/browse/browse.component.scss index baec3eb..4583e1a 100644 --- a/frontend/src/app/components/home/browse/browse.component.scss +++ b/frontend/src/app/components/home/browse/browse.component.scss @@ -84,3 +84,41 @@ section { padding: 24px; text-align: center; } + +// Resolution Buttons + +.resolution-controls { + display: flex; + justify-content: center; + gap: 12px; + margin: 18px 0; +} + +.resolution-btn { + background-color: $light-blue; + color: $white; + border: none; + border-radius: 4px; + padding: 6px 12px; + font-size: 16px; + cursor: pointer; + transition: background-color 0.3s ease, transform 0.2s ease; +} + +.resolution-btn.active { + background-color: $blue; +} + +.resolution-btn:disabled { + cursor: auto; +} + +.resolution-btn:not(.active):hover { + background-color: $blue; + transform: scale(1.05); +} + +.resolution-btn:focus { + outline: none; + box-shadow: 0 0 0 3px rgba(38, 143, 255, 0.5); +} diff --git a/frontend/src/app/components/home/browse/browse.component.ts b/frontend/src/app/components/home/browse/browse.component.ts index 67c7bb9..904124f 100644 --- a/frontend/src/app/components/home/browse/browse.component.ts +++ b/frontend/src/app/components/home/browse/browse.component.ts @@ -1,11 +1,17 @@ -import { Component, OnInit } from '@angular/core'; +import { + Component, + EventEmitter, + OnInit, + Output, + ViewChild, +} from '@angular/core'; import { HeaderComponent } from '../../../shared/components/header/header.component'; import { HeroBannerComponent } from './hero-banner/hero-banner.component'; import { CategoriesComponent } from './categories/categories.component'; import { AuthService } from '../../../services/auth.service'; import { MovieService } from '../../../services/movie.service'; import { CommonModule } from '@angular/common'; -import { VideoPlayerComponent } from './vjs-player/vjs-player.component'; +import { VideoPlayerComponent } from './video-player/video-player.component'; import { BtnSmallComponent } from '../../../shared/components/buttons/btn-small/btn-small.component'; import { UploadMovieComponent } from './upload-movie/upload-movie.component'; @@ -25,10 +31,12 @@ import { UploadMovieComponent } from './upload-movie/upload-movie.component'; styleUrl: './browse.component.scss', }) export class BrowseComponent implements OnInit { + @ViewChild(VideoPlayerComponent) videoPlayer!: VideoPlayerComponent; movies: any[] = []; currentMovie: any[] = []; playMovie: string = ''; uploadMovieOverview: boolean = false; + currentResolution: '480p' | '720p' | '1080p' = '720p'; constructor( private authService: AuthService, @@ -40,6 +48,13 @@ export class BrowseComponent implements OnInit { this.currentMovie.length === 0 ? this.loadRandomMovie() : null; } + changeResolution(resolution: '480p' | '720p' | '1080p') { + if (this.videoPlayer) { + this.videoPlayer.switchResolution(resolution); + this.currentResolution = resolution; + } + } + async loadAllMovies() { this.movies = await this.movieService.getAllMovies(); } @@ -66,8 +81,6 @@ export class BrowseComponent implements OnInit { } toggleUploadMovieOverview(value: any) { - console.log(value); - this.uploadMovieOverview = value; } } diff --git a/frontend/src/app/components/home/browse/hero-banner/hero-banner.component.html b/frontend/src/app/components/home/browse/hero-banner/hero-banner.component.html index c832fc1..254f691 100644 --- a/frontend/src/app/components/home/browse/hero-banner/hero-banner.component.html +++ b/frontend/src/app/components/home/browse/hero-banner/hero-banner.component.html @@ -28,8 +28,7 @@ '/media/videos/' + currentMovie[0]?.id + '/' + - currentMovie[0]?.file_name + - '_1080p.m3u8' + currentMovie[0]?.file_name ) " > diff --git a/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.html b/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.html index 25bf9d1..5a3ad1c 100644 --- a/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.html +++ b/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.html @@ -73,20 +73,19 @@ type="file" id="videoFile" name="videoFile" - #videoFile="ngModel" placeholder="Video file" - [(ngModel)]="movieData.videoFile" - [class.error-border]="!videoFile.valid && videoFile.touched" + (change)="onFileChange($event)" required /> -
- @if (!videoFile.valid && videoFile.touched) { -

Please enter the movie file

- } -
+
diff --git a/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.ts b/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.ts index 6b97f3c..3710eb5 100644 --- a/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.ts +++ b/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.ts @@ -3,6 +3,7 @@ import { Component, EventEmitter, Output } from '@angular/core'; import { FormsModule, NgForm } from '@angular/forms'; import { ErrorService } from '../../../../services/error.service'; import { BtnLargeComponent } from '../../../../shared/components/buttons/btn-large/btn-large.component'; +import { MovieService } from '../../../../services/movie.service'; @Component({ selector: 'app-upload-movie', standalone: true, @@ -17,10 +18,13 @@ export class UploadMovieComponent { title: '', description: '', filmGenre: '', - videoFile: '', + videoFile: null as File | null, }; - constructor(public errorService: ErrorService) {} + constructor( + public errorService: ErrorService, + private movieService: MovieService + ) {} stopPropagation(event: MouseEvent) { event.stopPropagation(); @@ -30,9 +34,30 @@ export class UploadMovieComponent { this.toggleUploadMovieOverview.emit(false); } + onFileChange(event: any) { + const file = event.target.files[0]; + if (file) { + this.movieData.videoFile = file; + } + } + async onSubmit(ngForm: NgForm) { if (ngForm.submitted && ngForm.form.valid) { - console.log('send'); + const formData = new FormData(); + formData.append('title', this.movieData.title); + formData.append('description', this.movieData.description); + formData.append('film_genre', this.movieData.filmGenre); + if (this.movieData.videoFile) { + formData.append('video_file', this.movieData.videoFile); + } + try { + await this.movieService.uploadMovie(formData); + ngForm.resetForm(); + this.uploadMovieOverview(); + this.errorService.clearError(); + } catch (error) { + this.errorService.handleError(error); + } } } } diff --git a/frontend/src/app/components/home/browse/video-player/video-player.component.html b/frontend/src/app/components/home/browse/video-player/video-player.component.html new file mode 100644 index 0000000..531a91c --- /dev/null +++ b/frontend/src/app/components/home/browse/video-player/video-player.component.html @@ -0,0 +1 @@ + diff --git a/frontend/src/app/components/home/browse/vjs-player/vjs-player.component.scss b/frontend/src/app/components/home/browse/video-player/video-player.component.scss similarity index 100% rename from frontend/src/app/components/home/browse/vjs-player/vjs-player.component.scss rename to frontend/src/app/components/home/browse/video-player/video-player.component.scss diff --git a/frontend/src/app/components/home/browse/vjs-player/vjs-player.component.spec.ts b/frontend/src/app/components/home/browse/video-player/video-player.component.spec.ts similarity index 83% rename from frontend/src/app/components/home/browse/vjs-player/vjs-player.component.spec.ts rename to frontend/src/app/components/home/browse/video-player/video-player.component.spec.ts index f408b7f..254e8f1 100644 --- a/frontend/src/app/components/home/browse/vjs-player/vjs-player.component.spec.ts +++ b/frontend/src/app/components/home/browse/video-player/video-player.component.spec.ts @@ -1,8 +1,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { VideoPlayerComponent } from './vjs-player.component'; +import { VideoPlayerComponent } from './video-player.component'; -describe('VjsPlayerComponent', () => { +describe('VideoPlayerComponent', () => { let component: VideoPlayerComponent; let fixture: ComponentFixture; diff --git a/frontend/src/app/components/home/browse/video-player/video-player.component.ts b/frontend/src/app/components/home/browse/video-player/video-player.component.ts new file mode 100644 index 0000000..84d3786 --- /dev/null +++ b/frontend/src/app/components/home/browse/video-player/video-player.component.ts @@ -0,0 +1,78 @@ +import { Component, OnInit, OnDestroy, ElementRef, Input } from '@angular/core'; +import Hls from 'hls.js'; + +@Component({ + selector: 'app-video-player', + standalone: true, + templateUrl: './video-player.component.html', + styleUrls: ['./video-player.component.scss'], +}) +export class VideoPlayerComponent implements OnInit, OnDestroy { + @Input() playMovie: string = ''; + private hls: Hls | null = null; + private videoElement: HTMLVideoElement | null = null; + private resolutionUrls: { [key: string]: string } = {}; + + constructor(private elementRef: ElementRef) {} + + ngOnInit(): void { + this.videoElement = this.elementRef.nativeElement.querySelector( + 'video' + ) as HTMLVideoElement; + + if (this.playMovie) { + this.resolutionUrls = { + '480p': `${this.playMovie}_480p.m3u8`, + '720p': `${this.playMovie}_720p.m3u8`, + '1080p': `${this.playMovie}_1080p.m3u8`, + }; + + if (Hls.isSupported() && this.videoElement) { + this.hls = new Hls(); + this.hls.loadSource(this.resolutionUrls['720p']); // Default resolution + this.hls.attachMedia(this.videoElement); + this.hls.on(Hls.Events.MANIFEST_PARSED, () => { + this.videoElement?.play(); + }); + } else if ( + this.videoElement.canPlayType('application/vnd.apple.mpegurl') + ) { + this.videoElement.src = this.resolutionUrls['720p']; // Default resolution + this.videoElement.addEventListener('canplay', () => { + this.videoElement?.play(); + }); + } + + this.updateScreenDimensions(); + } else { + console.error('playMovie is not set.'); + } + } + + private updateScreenDimensions() { + if (this.videoElement) { + this.videoElement.style.width = `${window.innerWidth}px`; + this.videoElement.style.height = `${window.innerHeight}px`; + } + } + + ngOnDestroy(): void { + if (this.hls) { + this.hls.destroy(); + } + } + + // Method to switch resolution + public switchResolution(resolution: '480p' | '720p' | '1080p') { + if (this.resolutionUrls[resolution]) { + if (this.hls) { + this.hls.loadSource(this.resolutionUrls[resolution]); + this.hls.attachMedia(this.videoElement!); + } else if (this.videoElement) { + this.videoElement.src = this.resolutionUrls[resolution]; + } + } else { + console.error(`Resolution URL for ${resolution} not found.`); + } + } +} diff --git a/frontend/src/app/components/home/browse/vjs-player/vjs-player.component.html b/frontend/src/app/components/home/browse/vjs-player/vjs-player.component.html deleted file mode 100644 index 7244815..0000000 --- a/frontend/src/app/components/home/browse/vjs-player/vjs-player.component.html +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/frontend/src/app/components/home/browse/vjs-player/vjs-player.component.ts b/frontend/src/app/components/home/browse/vjs-player/vjs-player.component.ts deleted file mode 100644 index 057290e..0000000 --- a/frontend/src/app/components/home/browse/vjs-player/vjs-player.component.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - Component, - OnInit, - OnDestroy, - ElementRef, - Input, - HostListener, -} from '@angular/core'; -import videojs from 'video.js'; -import log from 'video.js/dist/types/utils/log'; - -@Component({ - selector: 'app-video-player', - standalone: true, - templateUrl: './vjs-player.component.html', - styleUrls: ['./vjs-player.component.scss'], -}) -export class VideoPlayerComponent implements OnInit, OnDestroy { - @Input() playMovie: string = ''; - private player: any; - private screenWidth: number = 0; - private screenHeight: number = 0; - - constructor(private elementRef: ElementRef) {} - - ngOnInit(): void { - this.updateScreenDimensions(); - this.player = videojs( - this.elementRef.nativeElement.querySelector('video'), - { - controls: true, - autoplay: true, - height: this.screenHeight, - width: this.screenWidth, - preload: 'auto', - sources: [ - { - src: this.playMovie, - type: 'application/x-mpegURL', - }, - ], - } - ); - } - - private updateScreenDimensions() { - this.screenWidth = window.innerWidth; - this.screenHeight = window.innerHeight; - } - - ngOnDestroy(): void { - if (this.player) { - this.player.dispose(); - } - } -} diff --git a/frontend/src/app/services/movie.service.ts b/frontend/src/app/services/movie.service.ts index 443e357..834a86f 100644 --- a/frontend/src/app/services/movie.service.ts +++ b/frontend/src/app/services/movie.service.ts @@ -21,6 +21,15 @@ export class MovieService { return lastValueFrom(this.http.get(url, { headers })); } + async uploadMovie(formData: FormData) { + const headers = this.getAuthHeaders(); + await lastValueFrom( + this.http.post(`${environment.baseUrl}/content/upload/`, formData, { + headers, + }) + ); + } + private getAuthHeaders(): HttpHeaders { let authToken = localStorage.getItem('authToken'); if (!authToken) {