Difference between revisions of "macOS Video Player"

From Free Pascal wiki
Jump to navigationJump to search
m (Remove under construction warning)
Line 304: Line 304:
  
 
   {$ifdef DEBUG}
 
   {$ifdef DEBUG}
   // Status information for debugginh
+
   // Status information for debugging
 
   if(myPlayer.Status = AVPlayerStatusFailed) then
 
   if(myPlayer.Status = AVPlayerStatusFailed) then
 
     ShowMessage('AVplayer status: failed')
 
     ShowMessage('AVplayer status: failed')

Revision as of 08:11, 3 November 2021

macOSlogo.png

This article applies to macOS only.

See also: Multiplatform Programming Guide

Overview

AVPlayer is a controller object used to manage the playback and timing of a media asset. You can use an AVPlayer to play local and remote file-based media, such as QuickTime movies and MP3 audio files, as well as audiovisual media served using HTTPS Live Streaming. AVPlayer is for playing a single media asset at a time. You can reuse the player instance to play additional media assets using its replaceCurrentItemWithPlayerItem: method. The AVFoundation framework also provides a subclass of AVPlayer, called AVQueuePlayer, to create and manage the queuing of media assets to be played sequentially.

An AVPlayer play media assets, which the AVFoundation framework models using the AVAsset class. AVAsset only models the static aspects of the media, such as its duration or creation date, and on its own, is unsuitable for playback with an AVPlayer. To play an asset, you need to create an instance of its dynamic counterpart found in AVPlayerItem. This object models the timing and presentation state of an asset played by an instance of AVPlayer.

AVPlayer and AVPlayerItem are nonvisual objects which are unable to present an asset’s video onscreen. There are two main approaches you can take to present your video content onscreen:

  • AVKit: Apple recommends this as the best way to present your video content with the AVKit fromaework’s AVPlayerView class which presents the video content, along with playback controls and other media features giving you a full-featured playback experience.
  • AVPlayerLayer: If you want to build a custom interface for your player, use AVPlayerLayer. The player layer can be set as a view’s backing layer or can be added directly to the layer hierarchy. Unlike AVPlayerView, a player layer doesn’t present any playback controls, it just presents the visual content onscreen. You need to build the playback transport controls to play, pause, and seek through the media.

Supported file types

AVPlayer supports media with the following Uniform Type Identifiers:

public.mpeg public.mpeg-2-video
public.avi public.aifc-audio
public.aac-audio public.mpeg-4
public.au-audio public.aiff-audio
public.mp2 public.3gpp2
public.ac3-audio public.mp3
public.mpeg-2-transport-stream public.3gpp
public.mpeg-4-audio

AvPlayer supports media with the following file extensions:

caf ttml au ts mqv pls flac dv amr mp1
mp3 ac3 loas 3gp aifc m2v m2t m4b m2a m4r
aa webvtt aiff m4a scc mp4 m4p mp2 eac3 mpa
vob scc aax mpg wav mov itt xhe m3u m3u8
mts mod vtt m4v 3g2 sc2 aac mp4 vtt m1a
mp2 avi

AVPlayer vs AVAudioPlayer

AVPlayer also plays audio only files as does AVAudioPlayer, so what are the advantages or disadvantages of using AVPlayer?

  • The documentation for AVPlayer states that the player works equally well with local and remote media files
  • The documentation for AVAudioPlayer states that Apple recommends that you use this class for audio playback unless you are playing audio captured from a network stream.
  • AVPlayer has more capabilities - see Apple's documentation (links below).

Application Transport Security

Security. Yes, it sometimes gets in the way. If you try loading your remote content using an HTTP scheme, you will be prevented from doing so by Application Transport Security. To overcome this unwelcome security intervention, you will need to add this to your application's .plist file:

  <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoadsForMedia</key>
            <true/>
    </dict>

In the code example below, loading the Apple URL https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8 may require this addition to the .plist file:

  <key>NSAppTransportSecurity</key>
    <dict>
      <key>NSExceptionDomains</key>
        <dict>
          <key>devstreaming-cdn.apple.com</key>
            <dict>
              <key>NSExceptionRequiresForwardSecrecy</key>
                <false/>
            </dict>
        </dict>
    </dict>

I say may because I started this code nearly two years ago I had included it, but with the effluxion of time no longer recall why it was needed. When testing today, the application worked without it - <shrug/>.

Example code using AVPlayerLayer for a custom player

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 debugging
  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