Notifications
Clear all
Topic starter
Hi,
you might be wondering how to create a media player. Technically, all you need is a simple video element. However, in most cases, additional functionality is required, which is why libraries like Shaka Player or DashJS are commonly used.
This class is designed to help you build a media player. While you could theoretically place everything inside TQTXVideo.
Due to nostalgic reasons I chose to structure it similarly to Delphi, where the player logic and the player window are separated into different classes.
A sample implementation would look like this:
var LVideo = TQTXVideo.Create(self, procedure(AVideoElement: TQTXVideo) begin var LPlayer = TQTXBasePlayer.createDefault(AVideoElement); LPlayer.OpenMedia('https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'); AVideoElement.Style.width := '100%'; AVideoElement.Style.height := '100%'; LPlayer.OnLoadedData := procedure(Sender: TObject) begin LPlayer.Play(); end; end);
The whole unit is the following one.
unit qtx.mediaplayer; interface uses qtx.sysutils, qtx.classes, qtx.dom.widgets, qtx.dom.types; type TQTXVideo = class; TQTXVideoConstructor = procedure (AVideoElement: TQTXVideo); TQTXVideo = class(TQTXWidget) protected FVideoHandle: THandle; public constructor Create(AOwner: TQTXComponent; CB: TQTXVideoConstructor); reintroduce; virtual; end; TQTXBasePlayer = class protected FVideoHandle: THandle; FOnLoadedData: TNotifyEvent; FPoster: String; procedure InitializePlayer;virtual; procedure SetPoster(const AValue: String);virtual; public constructor Create(const AVideo: TQTXVideo); procedure Play;virtual; procedure Stop;virtual; procedure OpenMedia(AMediaFile: String; ALicenseServer, ACertServer: String = '');virtual; function IsPlaying: Boolean; property OnLoadedData: TNotifyEvent read FOnLoadedData write FOnLoadedData; property Poster: String read FPoster write SetPoster; class function createDefault(const AVideo: TQTXVideo): TQTXBasePlayer; end; TQTXShakaPlayer = class(TQTXBasePlayer) private FVideoPlayer: THandle; protected procedure InitializePlayer;override; public procedure OpenMedia(AMediaFile: String; ALicenseServer, ACertServer: String = '');override; end; TQTXShakaUIPlayer = class(TQTXShakaPlayer) protected FShakaUI: THandle; procedure InitializePlayer;override; procedure SetPoster(const AValue: String);override; end; implementation var FInitialized: Boolean = false; document external "document": JDocument; { TQTXBasePlayer } class function TQTXBasePlayer.createDefault(const AVideo: TQTXVideo): TQTXBasePlayer; begin result := TQTXShakaUIPlayer.create(AVideo); end; constructor TQTXBasePlayer.Create(const AVideo: TQTXVideo); begin FVideoHandle := AVideo.FVideoHandle; InitializePlayer; end; procedure TQTXBasePlayer.Play; begin if FVideoHandle <> nil then FVideoHandle.play(); end; procedure TQTXBasePlayer.Stop; begin if FVideoHandle <> nil then FVideoHandle.pause(); end; procedure TQTXBasePlayer.InitializePlayer; begin end; procedure TQTXBasePlayer.OpenMedia(AMediaFile: String; ALicenseServer, ACertServer:String = ''); begin if FVideoHandle <> nil then FVideoHandle.src := AMediaFile; end; function TQTXBasePlayer.IsPlaying(): Boolean; begin result := (FVideoHandle <> nil) and (not FVideoHandle.paused); end; procedure TQTXBasePlayer.SetPoster(const AValue: String); begin FPoster := AValue; end; { TQTXShakaPlayer } procedure TQTXShakaPlayer.InitializePlayer; procedure doLoadedData(); begin if assigned(FOnLoadedData) then FOnLoadedData(self); end; procedure performError(AError: String); begin Stop; end; begin if not FInitialized then begin FInitialized := true; asm shaka.polyfill.installAll(); shaka.polyfill.PatchedMediaKeysApple.install(); end; end; asm const videoElement = @self.FVideoHandle; videoElement.onloadeddata = @doLoadedData; var currentTime = []; videoElement.addEventListener("timeupdate", function() { if (!videoElement.seeking) { currentTime = videoElement.currentTime; } }); let player; if (shaka.Player.isBrowserSupported()) { player = new shaka.Player(); player.attach(videoElement); player.configure('preferredTextLanguage', ''); player.addEventListener('error', function(event) { var error = event.detail; @performError(error.code + ' - ' + error.message); }); videoElement.player = player; @self.FVideoPlayer = player; } end; end; procedure TQTXShakaPlayer.OpenMedia(AMediaFile: String; ALicenseServer, ACertServer: String=''); begin asm const player = @FVideoPlayer; const videoElement = @FVideoHandle; const playerConfig = { manifest: { dash: { ignoreMinBufferTime: true, }, defaultPresentationDelay: 0 }, streaming: { lowLatencyMode: true, inaccurateManifestTolerance: 0, rebufferingGoal: 0.1, preferNativeHls: true, }, abr: { enabled: true, useNetworkInformation: false, defaultBandwidthEstimate: 1000000 / 240, switchInterval: 120, }, }; if (@ALicenseServer !== "null" && @ALicenseServer !== null) { playerConfig.drm = { advanced: { "com.apple.fps": { serverCertificateUri: @ACertServer } }, servers: { "com.widevine.alpha": @ALicenseServer, "com.apple.fps": @ALicenseServer, }, }; } videoElement.playbackRate = 1.0; player.configure(playerConfig); player.load(@AMediaFile); end; end; { TQTXShakaUIPlayer } procedure TQTXShakaUIPlayer.InitializePlayer; begin inherited InitializePlayer; asm const video = @FVideoHandle; video.controls = false; video.style.width = '100%'; const player = @FVideoPlayer; player.configure('ui.addPictureInPictureButton', true); video.parentElement.addEventListener('keyup', function(event) { if (!video.paused && event.key.toLowerCase() === 'f') { event.preventDefault(); video.requestFullscreen(); } }); const ui = new shaka.ui.Overlay(player, video.parentElement, video); const config = { 'addSeekBar': true, 'seekBarColors': { base: 'rgba(255,255,255,.2)', buffered: 'rgba(255,255,255,.4)', played: '#2f80ed', } } ui.configure(config); @FShakaUI = ui; end; end; procedure TQTXShakaUIPlayer.SetPoster(const AValue: String); begin inherited SetPoster(AValue); FVideoHandle.Poster := AValue; end; { TQTXVideo } constructor TQTXVideo.Create(AOwner: TQTXComponent; CB: TQTXVideoConstructor); begin inherited Create(AOwner, procedure(Widget: TQTXWidget) begin FVideoHandle := document.createElement('video'); Widget.Handle.appendChild(FVideoHandle); PositionMode := TQTXWidgetPositionMode.cpAbsolute; if assigned(CB) then CB(self); end); end; (* Example Player Add this to your index.html <script src="https://cdnjs.cloudflare.com/ajax/libs/shaka-player/4.12.6/shaka-player.compiled.js" ></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/shaka-player/4.12.6/shaka-player.ui.min.js" ></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/shaka-player/4.12.6/controls.min.css" /> *) end.
Posted : 31/01/2025 10:59 am