Code/MediaSource.cs
using System;
using System.Globalization;
using System.Linq;

namespace Bimp;

/// <summary>
/// Url helpers. Direct media files are played as-is; YouTube and the other sites the native resolver
/// understands go through <see cref="Resolver.BimpResolver"/>.
/// </summary>
public static class MediaSource
{
	static readonly string[] AudioExtensions = { "mp3", "ogg", "oga", "opus", "flac", "wav", "m4a", "aac" };
	static readonly string[] VideoExtensions = { "mp4", "webm", "mkv", "mov", "m4v", "webp" };

	/// <summary>
	/// Try to parse the user's input into an absolute http(s) url. Adds https:// if missing.
	/// </summary>
	public static bool TryNormalize( string input, out Uri uri )
	{
		uri = null;
		if ( string.IsNullOrWhiteSpace( input ) ) return false;

		input = input.Trim();
		if ( !input.Contains( "://" ) ) input = "https://" + input;

		if ( !Uri.TryCreate( input, UriKind.Absolute, out uri ) ) return false;
		return uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps || Resolver.Live.LiveStream.IsRtsp( uri );
	}

	static string Extension( Uri uri ) => System.IO.Path.GetExtension( uri.AbsolutePath ).TrimStart( '.' ).ToLowerInvariant();

	public static bool IsDirectMedia( Uri uri )
	{
		var ext = Extension( uri );
		return AudioExtensions.Contains( ext ) || VideoExtensions.Contains( ext );
	}

	public static bool IsAudioExtension( Uri uri ) => AudioExtensions.Contains( Extension( uri ) );

	/// <summary>
	/// A live stream: RTSP (tunnelled over HTTP), MPEG-TS over HTTP, or HLS. Each client connects to it itself.
	/// </summary>
	public static MediaQueueItem LiveItem( Uri uri, bool forceAudio )
	{
		var name = System.IO.Path.GetFileName( uri.AbsolutePath );
		return new MediaQueueItem
		{
			Url = uri.ToString(),
			PlayUrl = uri.ToString(),
			Title = string.IsNullOrWhiteSpace( name ) ? uri.Host : $"{uri.Host} - {Uri.UnescapeDataString( name )}",
			Duration = 0,
			IsLive = true,
			AudioOnly = forceAudio,
			SeekByReload = false,
		};
	}

	/// <summary>
	/// A url the engine plays itself.
	/// </summary>
	public static MediaQueueItem DirectItem( Uri uri, bool forceAudio )
	{
		var name = Uri.UnescapeDataString( System.IO.Path.GetFileName( uri.AbsolutePath ) );
		if ( string.IsNullOrWhiteSpace( name ) ) name = uri.Host;

		return new MediaQueueItem
		{
			Url = uri.ToString(),
			PlayUrl = uri.ToString(),
			Title = name,
			Duration = 0, // learned from the player once it loads
			AudioOnly = forceAudio || IsAudioExtension( uri ),
			SeekByReload = false,
		};
	}

	/// <summary>
	/// Format seconds as m:ss or h:mm:ss.
	/// </summary>
	public static string FormatTime( float seconds )
	{
		if ( float.IsNaN( seconds ) || seconds < 0 ) seconds = 0;
		var t = TimeSpan.FromSeconds( seconds );
		return t.TotalHours >= 1 ? t.ToString( @"h\:mm\:ss" ) : t.ToString( @"m\:ss" );
	}
}