Forum

Notifications
Clear all

Mediaplayer

1 Posts
1 Users
0 Reactions
45 Views
(@hackbart)
Posts: 25
Trusted Member
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
Share: