mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-17 16:50:53 +00:00
refactor(m3u): optimize URL generation for M3U streams and logos
- Precompute base URLs for M3U stream and logo paths to reduce redundant calls to `build_absolute_uri_with_port()`, improving performance. - Update the generation of EPG and stream URLs to utilize the precomputed base URL, ensuring consistency and efficiency in URL handling. - Enhance the handling of logo URLs by consolidating logic for cached and direct logo retrieval, streamlining the process and reducing overhead.
This commit is contained in:
parent
a8a3c70e55
commit
86f39cb7f1
2 changed files with 15 additions and 9 deletions
|
|
@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Performance
|
||||
|
||||
- **M3U stream and logo URLs no longer call `build_absolute_uri_with_port()` per channel.** The host/port/scheme base URL and logo path prefix/suffix are computed once per request; each row appends only the channel UUID or logo ID (and shared query-string suffix), matching the existing XC `get_live_streams` and HDHR lineup patterns.
|
||||
- **`get_vod_streams` and `get_series` XC API response times reduced significantly for large libraries.** Both endpoints previously loaded all active relations per title (one row per account that carries the same movie/series), then discarded duplicates in Python. With large libraries across multiple active accounts this fetched a multiple of N rows. Both queries now use a single `DISTINCT ON (movie_id/series_id)` pass over the relation table ordered by account priority, returning exactly one row per title. Logo URL construction (`reverse()` + `build_absolute_uri_with_port()`) is also computed once and reused via prefix/suffix string concatenation, matching the existing pattern in `get_live_streams`. The `m3u_account` JOIN in `get_series` was also removed (it was fetched but never read in the loop). Additionally, `get_series` previously had no `order_by` on `M3USeriesRelation`, so output order was non-deterministic (physical storage order). Both endpoints now sort alphabetically by title. Observed improvements: 18s → 12s for 48k movies; 9.5s → 3.5s for 11k series.
|
||||
- **Database connections are now managed by a persistent per-worker pool.** Previously each request opened a fresh TCP connection to PostgreSQL, paid a full authentication handshake, and closed the connection at request end. `django-db-geventpool` now maintains a pool of warm connections per uWSGI worker; requests borrow a connection and return it when done, eliminating connection-setup overhead on every request. Pool size is bounded (`MAX_CONNS=8` per worker, `REUSE_CONNS=3` warm connections kept idle) to stay comfortably within PostgreSQL's default `max_connections=100` across all uWSGI workers, Celery workers, and Daphne. — Thanks [@JCBird1012](https://github.com/JCBird1012)
|
||||
- **Reduced Redis round-trips on the Stats page channel status endpoint.** `get_basic_channel_info` was making up to 6 individual `HGET` calls per connected client plus a redundant `HGET` for `TOTAL_BYTES` (already present in the preceding `HGETALL` result). Client metadata is now fetched with a single `HMGET` per client, and `TOTAL_BYTES` is read from the already-fetched hash. Under load with many active streams this significantly reduces the time each uWSGI worker holds the GIL servicing the stats endpoint, reducing the chance of concurrent requests from other pages timing out with a 503. The same `HGET`-to-`HMGET` consolidation was applied to `stream_ts` and `get_user_active_connections`. The Stats page frontend was also fixed to fire the initial fetch only once on mount (previously two `useEffect` hooks both triggered an immediate fetch on load). — Thanks [@JCBird1012](https://github.com/JCBird1012)
|
||||
|
|
|
|||
|
|
@ -205,11 +205,11 @@ def generate_m3u(request, profile_name=None, user=None):
|
|||
xc_username = request.GET.get('username')
|
||||
xc_password = request.GET.get('password')
|
||||
is_xc_request = user is not None and xc_username and xc_password
|
||||
_base_url = build_absolute_uri_with_port(request, '')
|
||||
|
||||
if is_xc_request:
|
||||
# This is an XC API request - use XC-style EPG URL
|
||||
base_url = build_absolute_uri_with_port(request, '')
|
||||
epg_url = f"{base_url}/xmltv.php?username={xc_username}&password={xc_password}"
|
||||
epg_url = f"{_base_url}/xmltv.php?username={xc_username}&password={xc_password}"
|
||||
# Build the query-string suffix for stream URLs once - it's the same for every channel
|
||||
xc_qs = {}
|
||||
if output_profile_id:
|
||||
|
|
@ -239,6 +239,13 @@ def generate_m3u(request, profile_name=None, user=None):
|
|||
# Add x-tvg-url and url-tvg attribute for EPG URL
|
||||
m3u_content = f'#EXTM3U x-tvg-url="{epg_url}" url-tvg="{epg_url}"\n'
|
||||
|
||||
# Host/port/scheme are constant per request; precompute URL prefixes once.
|
||||
_stream_url_prefix = None if is_xc_request else f"{_base_url}/proxy/ts/stream/"
|
||||
_sample_logo_path = reverse("api:channels:logo-cache", args=[0])
|
||||
_logo_prefix_raw, _, _logo_suffix_raw = _sample_logo_path.partition("/0/")
|
||||
_logo_url_prefix = _base_url + _logo_prefix_raw + "/"
|
||||
_logo_url_suffix = "/" + _logo_suffix_raw
|
||||
|
||||
# Start building M3U content
|
||||
channel_count = 0
|
||||
for channel in channels:
|
||||
|
|
@ -268,8 +275,7 @@ def generate_m3u(request, profile_name=None, user=None):
|
|||
tvg_logo = ""
|
||||
if effective_logo:
|
||||
if use_cached_logos:
|
||||
# Use cached logo as before
|
||||
tvg_logo = build_absolute_uri_with_port(request, reverse('api:channels:logo-cache', args=[effective_logo.id]))
|
||||
tvg_logo = f"{_logo_url_prefix}{effective_logo.id}{_logo_url_suffix}"
|
||||
else:
|
||||
# Try to find direct logo URL from channel's streams
|
||||
direct_logo = effective_logo.url if effective_logo.url.startswith(('http://', 'https://')) else None
|
||||
|
|
@ -277,7 +283,7 @@ def generate_m3u(request, profile_name=None, user=None):
|
|||
if direct_logo:
|
||||
tvg_logo = direct_logo
|
||||
else:
|
||||
tvg_logo = build_absolute_uri_with_port(request, reverse('api:channels:logo-cache', args=[effective_logo.id]))
|
||||
tvg_logo = f"{_logo_url_prefix}{effective_logo.id}{_logo_url_suffix}"
|
||||
|
||||
# create possible gracenote id insertion
|
||||
tvc_guide_stationid = ""
|
||||
|
|
@ -293,7 +299,7 @@ def generate_m3u(request, profile_name=None, user=None):
|
|||
|
||||
# Determine the stream URL based on request type
|
||||
if is_xc_request:
|
||||
stream_url = f"{base_url}/live/{xc_username}/{xc_password}/{channel.id}{xc_qs_suffix}"
|
||||
stream_url = f"{_base_url}/live/{xc_username}/{xc_password}/{channel.id}{xc_qs_suffix}"
|
||||
elif use_direct_urls:
|
||||
# Try to get the first stream's direct URL
|
||||
all_streams = channel.streams.all()
|
||||
|
|
@ -303,11 +309,10 @@ def generate_m3u(request, profile_name=None, user=None):
|
|||
stream_url = first_stream.url
|
||||
else:
|
||||
# Fall back to proxy URL if no direct URL available
|
||||
stream_url = build_absolute_uri_with_port(request, f"/proxy/ts/stream/{channel.uuid}")
|
||||
stream_url = f"{_stream_url_prefix}{channel.uuid}"
|
||||
else:
|
||||
# Standard behavior - use proxy URL
|
||||
base_stream_url = build_absolute_uri_with_port(request, f"/proxy/ts/stream/{channel.uuid}")
|
||||
stream_url = f"{base_stream_url}{proxy_qs_suffix}"
|
||||
stream_url = f"{_stream_url_prefix}{channel.uuid}{proxy_qs_suffix}"
|
||||
|
||||
m3u_content += extinf_line + stream_url + "\n"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue