bugfixes, clean code, switch vjs player to hls, optimized backend & play video in different resolutions

This commit is contained in:
Chneemann 2024-08-14 14:00:27 +02:00
parent 27232e1421
commit c686296aeb
22 changed files with 264 additions and 617 deletions

View file

@ -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
resource_class = VideoResource
readonly_fields = ('file_name',)
list_display = ('title', 'file_name', 'created_at')

View file

@ -15,4 +15,5 @@ FILM_GENRES = [
('musical', 'Musical'),
('war', 'War'),
('western', 'Western'),
('other', 'Miscellaneous'),
]

View file

@ -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}'
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)

View file

@ -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):
"""

View file

@ -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.

View file

@ -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)
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)

View file

@ -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),

View file

@ -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",

View file

@ -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",

View file

@ -1,5 +1,5 @@
<section>
@if (currentMovie.length < 0) {
@if (currentMovie[0]) {
<!-- If at least one film has been uploaded -->
@if (playMovie === "") {
<app-header [browse]="true"></app-header>
@ -18,6 +18,38 @@
<div class="back-button" (click)="closeVideo()">
<img src="./../../../../assets/img/back.svg" alt="Back" />
</div>
<div class="resolution-controls">
<button
[ngClass]="{
'resolution-btn': true,
active: currentResolution === '480p'
}"
[disabled]="currentResolution === '480p'"
(click)="changeResolution('480p')"
>
480p
</button>
<button
[ngClass]="{
'resolution-btn': true,
active: currentResolution === '720p'
}"
[disabled]="currentResolution === '720p'"
(click)="changeResolution('720p')"
>
720p
</button>
<button
[ngClass]="{
'resolution-btn': true,
active: currentResolution === '1080p'
}"
[disabled]="currentResolution === '1080p'"
(click)="changeResolution('1080p')"
>
1080p
</button>
</div>
<div class="logo">
<img src="./../../../../assets/img/logo_ci.svg" alt="Logo" />
</div>

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -28,8 +28,7 @@
'/media/videos/' +
currentMovie[0]?.id +
'/' +
currentMovie[0]?.file_name +
'_1080p.m3u8'
currentMovie[0]?.file_name
)
"
></app-btn-large>

View file

@ -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
/>
<div class="error-msg">
@if (!videoFile.valid && videoFile.touched) {
<p>Please enter the movie file</p>
}
</div>
<div class="error-msg"></div>
<app-btn-large
[value]="'Get Started'"
[disabled]="!movieData.title"
[value]="'Upload Movie'"
[disabled]="
!movieData.title ||
!movieData.description ||
!movieData.filmGenre ||
!movieData.videoFile
"
></app-btn-large>
</form>
</div>

View file

@ -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);
}
}
}
}

View file

@ -0,0 +1 @@
<video controls></video>

View file

@ -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<VideoPlayerComponent>;

View file

@ -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.`);
}
}
}

View file

@ -1,3 +0,0 @@
<video id="my-video" class="video-js vjs-default-skin">
<source src="{{ playMovie }}" type="application/x-mpegURL" />
</video>

View file

@ -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();
}
}
}

View file

@ -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) {