From a6137fdbea0d6108c7f4dfcbb31bc9b88a5ae33a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Silvani?= Date: Mon, 5 Feb 2024 12:23:39 -0300 Subject: [PATCH 1/6] feat: Add optional test_rtmp_url field to Event This will be used to designate a testing RTMP server, which will be used as fallback when stream is not active. --- events/migrations/0021_event_test_rtmp_url.py | 19 +++++++++++++++++++ events/models.py | 14 +++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 events/migrations/0021_event_test_rtmp_url.py diff --git a/events/migrations/0021_event_test_rtmp_url.py b/events/migrations/0021_event_test_rtmp_url.py new file mode 100644 index 0000000..9efd4fd --- /dev/null +++ b/events/migrations/0021_event_test_rtmp_url.py @@ -0,0 +1,19 @@ +# Generated by Django 3.1.14 on 2024-02-05 15:23 + +from django.db import migrations +import events.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('events', '0020_stream_tags'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='test_rtmp_url', + field=events.models.RTMPURLField(blank=True, null=True), + ), + ] diff --git a/events/models.py b/events/models.py index a4ba6a8..3a15607 100644 --- a/events/models.py +++ b/events/models.py @@ -66,8 +66,9 @@ class Event(models.Model): ends_at = models.DateTimeField() active = models.BooleanField(default=True) preparation_time = models.PositiveIntegerField(default=5) - rtmp_url = RTMPURLField(blank=True, null=True) public_rtmp_url = RTMPURLField(blank=True, null=True) + rtmp_url = RTMPURLField(blank=True, null=True) + test_rtmp_url = RTMPURLField(blank=True, null=True) contact_email = models.EmailField(blank=True, null=True) def __str__(self): @@ -93,6 +94,11 @@ def resolved_rtmp_url(self): if self.rtmp_url: return resolve_url(self.rtmp_url) + @property + def resolved_test_rtmp_url(self): + if self.test_rtmp_url: + return resolve_url(self.test_rtmp_url) + @property def duration(self): if self.starts_at and self.ends_at: @@ -230,7 +236,9 @@ def __str__(self): class StreamArchiveURL(models.Model): - stream = models.ForeignKey(Stream, on_delete=models.CASCADE, related_name="archive_urls") + stream = models.ForeignKey( + Stream, on_delete=models.CASCADE, related_name="archive_urls" + ) url = models.URLField() name = models.CharField(max_length=255, blank=True) @@ -238,4 +246,4 @@ def __str__(self): return self.url class Meta: - unique_together = ("stream", "url") \ No newline at end of file + unique_together = ("stream", "url") From 0c0b4be2a477276020f45c4c2c59b9c11c8c8be2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Silvani?= Date: Mon, 5 Feb 2024 12:28:16 -0300 Subject: [PATCH 2/6] feat: Redirect to test stream when not active if event has a test RTMP server set --- events/tests.py | 4 ++-- events/views.py | 27 ++++++++++++++++++++------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/events/tests.py b/events/tests.py index 1094dc4..e5fca48 100644 --- a/events/tests.py +++ b/events/tests.py @@ -207,8 +207,8 @@ def test_update_stream_set_archive_urls(self): data = { "archive_urls": [ - { "url": "https://archive.org/foo", "name": "archive.org" }, - { "url": "https://youtube.com/foo", "name": "youtube.com" }, + {"url": "https://archive.org/foo", "name": "archive.org"}, + {"url": "https://youtube.com/foo", "name": "youtube.com"}, ] } response = self.client.patch( diff --git a/events/views.py b/events/views.py index 69f90dc..b3769ea 100644 --- a/events/views.py +++ b/events/views.py @@ -84,22 +84,35 @@ def on_publish(request): # Check if stream is valid if not stream.is_valid_at(now): - print("[PUBLISH] Stream is not valid at %s" % (now)) - return HttpResponseForbidden("Stream is not valid now") + # If event has a test RTMP URL, redirect to it + if stream.event.test_rtmp_url: + print( + "[PUBLISH] Stream is not valid at %s. Redirect to Test RTMP URL." + % (now) + ) + return RtmpRedirect(stream.resolved_test_rtmp_url) + else: + # Otherwise, deny the stream + print("[PUBLISH] Stream is not valid at %s" % (now)) + return HttpResponseForbidden("Stream is not valid now") # If event has a custom RTMP URL, redirect to it if stream.event.rtmp_url: if stream.is_preparing_at(now): - print( - "[PUBLISH] Stream is preparing and not active yet. Allow but do not redirect." - ) - return HttpResponse("OK") + if stream.event.test_rtmp_url: + print( + "[PUBLISH] Stream is preparing and not active yet. Redirect to Test RTMP URL." + ) + return RtmpRedirect(stream.resolved_test_rtmp_url) + else: + print("[PUBLISH] Stream is preparing and not active yet. Allow.") + return HttpResponse("OK") else: # Set the stream live stream.live_at = now stream.save() - print("[PUBLISH] Stream is active. Allow and redirect.") + print("[PUBLISH] Stream is active. Allow and redirect to custom RTMP URL.") return RtmpRedirect(stream.resolved_rtmp_url) else: print("[PUBLISH] Stream is active. Allow.") From 93de41609c07767609189d5ad6586a4f2e8b07ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Silvani?= Date: Mon, 5 Feb 2024 12:29:25 -0300 Subject: [PATCH 3/6] chore: Git ignore muxy.db (default db name) --- .gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index ec18e8e..94023b3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ +.env* +.venv/ .vscode/ __pycache__/ -db.sqlite3 -.env* +muxy.db static/ -.venv/ From 30c1d0d3c82f8d1e4fc59a79fc943ff7cff485ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Silvani?= Date: Mon, 5 Feb 2024 17:49:27 -0300 Subject: [PATCH 4/6] fix: Update comments --- events/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/events/views.py b/events/views.py index b3769ea..028ebc6 100644 --- a/events/views.py +++ b/events/views.py @@ -82,18 +82,18 @@ def on_publish(request): now = timezone.now() - # Check if stream is valid + # If stream is not valid (i.e. not preparing or inactive) if not stream.is_valid_at(now): # If event has a test RTMP URL, redirect to it if stream.event.test_rtmp_url: print( - "[PUBLISH] Stream is not valid at %s. Redirect to Test RTMP URL." + "[PUBLISH] Stream is not valid now (%s). Redirect to Test RTMP URL." % (now) ) return RtmpRedirect(stream.resolved_test_rtmp_url) else: # Otherwise, deny the stream - print("[PUBLISH] Stream is not valid at %s" % (now)) + print("[PUBLISH] Stream is not valid now (%s)" % (now)) return HttpResponseForbidden("Stream is not valid now") # If event has a custom RTMP URL, redirect to it From c10f7c9ace3596361f1f0a9168b37f42da41dd12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Silvani?= Date: Mon, 5 Feb 2024 17:49:42 -0300 Subject: [PATCH 5/6] fix: Change default NGINX_RTMP_UPDATE_TIMEOUT to 10 seconds --- env.sample | 2 +- muxy/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/env.sample b/env.sample index ddffbc5..98096de 100644 --- a/env.sample +++ b/env.sample @@ -8,6 +8,6 @@ TIME_ZONE=UTC DB_PATH=./muxy.db -NGINX_RTMP_UPDATE_TIMEOUT=30 +NGINX_RTMP_UPDATE_TIMEOUT=10 EMAIL_FROM= diff --git a/muxy/settings.py b/muxy/settings.py index f8650e4..5dbc34e 100644 --- a/muxy/settings.py +++ b/muxy/settings.py @@ -136,7 +136,7 @@ "PAGE_SIZE": 1000, } -NGINX_RTMP_UPDATE_TIMEOUT = int(os.getenv("NGINX_RTMP_UPDATE_TIMEOUT", "30")) +NGINX_RTMP_UPDATE_TIMEOUT = int(os.getenv("NGINX_RTMP_UPDATE_TIMEOUT", "10")) EMAIL_BACKEND = os.getenv( "EMAIL_BACKEND", "django.core.mail.backends.console.EmailBackend" From 3c235494c2951eedc7c340483620f0e10ed4b5a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Silvani?= Date: Mon, 5 Feb 2024 18:00:28 -0300 Subject: [PATCH 6/6] feat: Define defaults via settings/env vars for RTMP server fields on Event --- env.sample | 4 +++ events/migrations/0022_auto_20240205_2058.py | 29 ++++++++++++++++++++ events/models.py | 23 +++++++++++++--- muxy/settings.py | 4 +++ 4 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 events/migrations/0022_auto_20240205_2058.py diff --git a/env.sample b/env.sample index 98096de..2a22c76 100644 --- a/env.sample +++ b/env.sample @@ -11,3 +11,7 @@ DB_PATH=./muxy.db NGINX_RTMP_UPDATE_TIMEOUT=10 EMAIL_FROM= + +DEFAULT_PUBLIC_RTMP_URL=rtmp:///live +DEFAULT_RTMP_URL=rtmp://localhost/main +DEFAULT_TEST_RTMP_URL=rtmp://localhost/test diff --git a/events/migrations/0022_auto_20240205_2058.py b/events/migrations/0022_auto_20240205_2058.py new file mode 100644 index 0000000..454f4cb --- /dev/null +++ b/events/migrations/0022_auto_20240205_2058.py @@ -0,0 +1,29 @@ +# Generated by Django 3.1.14 on 2024-02-05 20:58 + +from django.db import migrations +import events.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('events', '0021_event_test_rtmp_url'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='public_rtmp_url', + field=events.models.RTMPURLField(blank=True, default=events.models.get_default_public_rtmp_url, null=True), + ), + migrations.AlterField( + model_name='event', + name='rtmp_url', + field=events.models.RTMPURLField(blank=True, default=events.models.get_default_rtmp_url, null=True), + ), + migrations.AlterField( + model_name='event', + name='test_rtmp_url', + field=events.models.RTMPURLField(blank=True, default=events.models.get_default_test_rtmp_url, null=True), + ), + ] diff --git a/events/models.py b/events/models.py index 3a15607..fc8bf19 100644 --- a/events/models.py +++ b/events/models.py @@ -8,7 +8,6 @@ from autoslug import AutoSlugField from django.conf import settings -from django.contrib.auth.models import User from django.core import validators from django.core.exceptions import ValidationError from django.db import models @@ -57,6 +56,18 @@ class Meta(AbstractAPIKey.Meta): verbose_name_plural = "API keys" +def get_default_public_rtmp_url(): + return settings.DEFAULT_PUBLIC_RTMP_URL + + +def get_default_rtmp_url(): + return settings.DEFAULT_RTMP_URL + + +def get_default_test_rtmp_url(): + return settings.DEFAULT_TEST_RTMP_URL + + class Event(models.Model): name = models.CharField(max_length=200) slug = AutoSlugField(null=True, default=None, populate_from="name", unique="name") @@ -66,9 +77,13 @@ class Event(models.Model): ends_at = models.DateTimeField() active = models.BooleanField(default=True) preparation_time = models.PositiveIntegerField(default=5) - public_rtmp_url = RTMPURLField(blank=True, null=True) - rtmp_url = RTMPURLField(blank=True, null=True) - test_rtmp_url = RTMPURLField(blank=True, null=True) + public_rtmp_url = RTMPURLField( + blank=True, null=True, default=get_default_public_rtmp_url + ) + rtmp_url = RTMPURLField(blank=True, null=True, default=get_default_rtmp_url) + test_rtmp_url = RTMPURLField( + blank=True, null=True, default=get_default_test_rtmp_url + ) contact_email = models.EmailField(blank=True, null=True) def __str__(self): diff --git a/muxy/settings.py b/muxy/settings.py index 5dbc34e..0333e9d 100644 --- a/muxy/settings.py +++ b/muxy/settings.py @@ -200,3 +200,7 @@ CORS_ALLOW_HEADERS = list(default_headers) + [ STREAM_KEY_HEADER, ] + +DEFAULT_PUBLIC_RTMP_URL = os.getenv("DEFAULT_PUBLIC_RTMP_URL") +DEFAULT_RTMP_URL = os.getenv("DEFAULT_RTMP_URL") +DEFAULT_TEST_RTMP_URL = os.getenv("DEFAULT_TEST_RTMP_URL")