macOS Video Player

From Free Pascal wiki
Revision as of 05:51, 3 November 2021 by Trev (talk | contribs) (→‎Example code: Basic video player)
Jump to navigationJump to search
macOSlogo.png

This article applies to macOS only.

See also: Multiplatform Programming Guide

Warning-icon.png

Warning: Under construction

Overview

AVPlayer vs AVAudioPlayer

For audio, ...


Example code

unit Unit1;

{$mode objfpc}{$H+}
{$modeswitch objectivec1}
{$linkframework AvKit}
{$linkframework AVFoundation}

// Enable/Disbale DEBUGGING
//{$define DEBUG}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
  CTypes,
  MacOSAll,
  {$ifdef DEBUG}
  CocoaUtils,
  {$endif}
  CocoaAll;

type

  { TForm1 }

  TForm1 = class(TForm)
    ResumeButton: TButton;
    PauseButton: TButton;
    PlayButton: TButton;
    QuitButton: TButton;
    procedure PauseButtonClick(Sender: TObject);
    procedure PlayButtonClick(Sender: TObject);
    procedure QuitButtonClick(Sender: TObject);
    procedure ResumeButtonClick(Sender: TObject);
  private

  public

  end;

  AVPlayerStatus = NSInteger;
  AVPlayerItemStatus = NSInteger;
  AVKeyValueStatus = NSInteger;

  CMTimeValue = cint64;
  CMTimeValuePtr = ^CMTimeValue;
  CMTimeScale = cint32;
  CMTimeScalePtr = ^CMTimeScale;
  CMTimeEpoch = cint64;
  CMTimeEpochPtr = ^CMTimeEpoch;
  CMTimeFlags = cuint32;
  CMTimeFlagsPtr = ^CMTimeFlags;

  CMTime = record
    value: CMTimeValue;
    timescale: CMTimeScale;
    flags: CMTimeFlags;
    epoch: CMTimeEpoch;
  end;

  AVAsynchronousKeyValueLoadingProtocol = objcprotocol external name 'AVAsynchronousKeyValueLoading' required
    function statusOfValueForKey_error (key: NSString; outError: NSErrorPtr): AVKeyValueStatus;
      message 'statusOfValueForKey:error:';
    procedure loadValuesAsynchronouslyForKeys_completionHandler (keys: NSArray; handler: OpaqueCBlock);
      message 'loadValuesAsynchronouslyForKeys:completionHandler:';
  end;

  AVAsset = objcclass external (NSObject, NSCopyingProtocol, AVAsynchronousKeyValueLoadingProtocol)
    private
      _asset: id; //AVAssetInternal;
    public
      class function assetWithURL (URL: NSURL): id; message 'assetWithURL:';
      function duration: CMTime; message 'duration';
      function preferredRate: single; message 'preferredRate';
      function preferredVolume: single; message 'preferredVolume';
      function preferredTransform: CGAffineTransform; message 'preferredTransform';
      function naturalSize: CGSize; message 'naturalSize';

      { Adopted protocols }
      function copyWithZone (mzone: NSZonePtr): id; message 'copyWithZone:';
      procedure loadValuesAsynchronouslyForKeys_completionHandler (keys: NSArray; handler: OpaqueCBlock);
        message 'loadValuesAsynchronouslyForKeys:completionHandler:';
      function statusOfValueForKey_error (mkey: NSString; outError: NSErrorPtr): AVKeyValueStatus;
        message 'statusOfValueForKey:error:';
    end;

  AVPlayerItem = objcclass external (NSObject, NSCopyingProtocol)
  private
    _playerItem: id; //AVPlayerItemInternal;
  public
    class function playerItemWithURL (URL: NSURL): AVPlayerItem; message 'playerItemWithURL:';
    class function playerItemWithAsset (asset: AVAsset): AVPlayerItem; message 'playerItemWithAsset:';
    class function playerItemWithAsset_automaticallyLoadedAssetKeys (asset: AVAsset; automaticallyLoadedAssetKeys: NSArray): AVPlayerItem;
      message 'playerItemWithAsset:automaticallyLoadedAssetKeys:'; { available in 10_9, 7_0 }
    function initWithURL (URL: NSURL): id; message 'initWithURL:';
    function initWithAsset (asset: AVAsset): id; message 'initWithAsset:';
    function initWithAsset_automaticallyLoadedAssetKeys (asset: AVAsset; automaticallyLoadedAssetKeys: NSArray): id;
      message 'initWithAsset:automaticallyLoadedAssetKeys:'; { available in 10_9, 7_0 }
    function status: AVPlayerItemStatus; message 'status';
    function error: NSError; message 'error';

    { Adopted protocols }
    function copyWithZone (mzone: NSZonePtr): id; message 'copyWithZone:';
  end;

  AVPlayer = objcclass external (NSObject)
    private
      _player: id; //AVPlayerInternal;
    public
      class function playerWithURL (URL: NSURL): id; message 'playerWithURL:';
      class function playerWithPlayerItem (item: AVPlayerItem): id; message 'playerWithPlayerItem:';
      function initWithURL (URL: NSURL): id; message 'initWithURL:';
      function initWithPlayerItem (item: AVPlayerItem): id; message 'initWithPlayerItem:';
      function status: AVPlayerStatus; message 'status';
      function error: NSError; message 'error';
      procedure setVolume(newValue: Single); message 'setVolume:';
    end;

  AVURLAsset = objcclass external (AVAsset)
  private
    _URLAsset: id; //AVURLAssetInternal;
  public
    class function audiovisualTypes: NSArray; message 'audiovisualTypes'; { available in 10_7, 5_0 }
    class function audiovisualMIMETypes: NSArray; message 'audiovisualMIMETypes'; { available in 10_7, 5_0 }
    class function isPlayableExtendedMIMEType (extendedMIMEType: NSString): ObjCBOOL;
      message 'isPlayableExtendedMIMEType:'; { available in 10_7, 5_0 }
    class function URLAssetWithURL_options (URL: NSURL; options: NSDictionary): AVURLAsset;
      message 'URLAssetWithURL:options:';
    function initWithURL_options (URL: NSURL; options: NSDictionary): id;
      message 'initWithURL:options:';
    function URL: NSURL; message 'URL';
  end;

  AVPlayerPlaybackControl = objccategory external (AVPlayer)
    procedure setRate(newValue: single); message 'setRate:';
    function rate: single; message 'rate';
    procedure play; message 'play';
    procedure pause; message 'pause';
  end;

  AVPlayerLayer = objcclass external (CALayer)
    private
      _playerLayer: id; //AVPlayerLayerInternal;
    public
      class function playerLayerWithPlayer (player: AVPlayer): AVPlayerLayer;
        message 'playerLayerWithPlayer:';
      procedure setPlayer(newValue: AVPlayer); message 'setPlayer:';
      function player: AVPlayer; message 'player';
      procedure setVideoGravity(newValue: NSString); message 'setVideoGravity:';
      function videoGravity: NSString; message 'videoGravity';
      function isReadyForDisplay: ObjCBOOL; message 'isReadyForDisplay';
      function videoRect: CGRect; message 'videoRect';
  end;

const
  AVPlayerStatusUnknown = 0;
  AVPlayerStatusReadyToPlay = 1;
  AVPlayerStatusFailed = 2;

  AVPlayerItemStatusUnknown = 0;
  AVPlayerItemStatusReadyToPlay = 1;
  AVPlayerItemStatusFailed = 2;

var
  Form1: TForm1;
  myPlayer: AVPlayer = Nil;
  myAsset: AVAsset = Nil;
  myPlayerItem: AVPlayerItem = Nil;
  myPlayerLayer: AVPlayerLayer = Nil;
  myURL: NSUrl = Nil;
  myView: NSView = Nil;

implementation

{$R *.lfm}

{ TForm1 }

//
// Play video or audio asset
//
procedure TForm1.PlayButtonClick(Sender: TObject);
begin
  //
  // 1. Create a URL
  //
  myURL := NSURL.URLWithString(NSSTR(PAnsiChar('https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8')));
  //myURL := NSURL.fileURLWithPath(NSStr(PAnsiChar('/Users/trev/alt.mp4')));
  if(myURL = Nil) then
    begin
      ShowMessage('myURL creation failed!');
      exit;
    end;

  //
  // 2. Create AVAsset, AVPlayerItem and AVPlayer objects
  //
  myAsset := AVAsset.alloc.assetWithURL(myURL);
  myPlayerItem := AVPlayerItem.alloc.initWithAsset(myAsset);
  myPlayer := AVPlayer.alloc.initWithPlayerItem(myPlayerItem);

  // Wait until status is known (while loading AVAsset)
  while(myPlayer.Status = AVPlayerStatusUnknown)
    do
      application.processmessages;

  {$ifdef DEBUG}
  // Status information for debugginh
  if(myPlayer.Status = AVPlayerStatusFailed) then
    ShowMessage('AVplayer status: failed')
  else if(myPlayer.Status = AVPlayerStatusReadyToPlay) then
    ShowMessage('AVplayer status: ready to play');
  {$endif}

  //
  // 3. Create PlayerLayer to display video
  //
  myPlayerLayer :=  AVPlayerLayer.playerLayerWithPlayer(myPlayer);

  // enable resizing on the fly
  myPlayerLayer.setVideoGravity(NSStr('AVLayerVideoGravityResizeAspect'));
  myPlayerLayer.setAutoresizingMask(kCALayerHeightSizable or kCALayerWidthSizable);

  // setup border color and border width
  myPlayerLayer.setBorderWidth(3);
  myPlayerLayer.setBorderColor(NSColor.yellowColor.cgColor);

  // Make visible by adding PlayerLayer to Form1 View
  myView := NSView(Form1.Handle);
  myView.layer.addSublayer(myPlayerLayer);

  // Setup video frame size and bounds
  myPlayerLayer.setFrame(CGRectMake(0, 0, myView.frame.size.width, myView.frame.size.height - 100));
  myPlayerLayer.setBounds(CGRectMake(0, 0, myView.frame.size.width, myView.frame.size.height - 100));

  {$ifdef DEBUG}
  // Status Information for debugging
  ShowMessage('Frame Height: ' + FloatToStr(myView.frame.size.height) + LineEnding
  + 'Frame Width  : ' + FloatToStr(myView.frame.size.width) + LineEnding
  + 'Video gravity: ' + NSStringToString(myPlayerLayer.videoGravity) + LineEnding
  + 'Needs display (layer): ' + BoolToStr(myPlayerLayer.needsDisplay) + LineEnding);

  if(myPlayerLayer.isReadyForDisplay) then
    ShowMessage('PlayerLayer: Ready for display')
  else
    ShowMessage('PlayerLayer: Not ready for display');
  {$endif}

  //
  // 4. Play
  //
  myPlayer.setRate(1);      // play normal speed
  myPlayer.setVolume(0.5);  // 50% volume
  myPlayer.play;
end;


//
// Pause playback
//
procedure TForm1.PauseButtonClick(Sender: TObject);
begin
  myPlayer.pause;
end;

//
// Resume playback
//
procedure TForm1.ResumeButtonClick(Sender: TObject);
begin
  myPlayer.play;
end;

//
// Quit player
//
procedure TForm1.QuitButtonClick(Sender: TObject);
begin
  Close;
end;


end.

See also

External links

  • t/c