// ------------------------------------------------------------------------------
// DPF.iOS.AVPlayer Component
//
// Dadeh Pardazane Faragir ( DPF ) Co.
//
// Web: http://www.dpfaragir.com
//
// Developed By: Babak Yaghoobi
//
// Email #1: yaghoobi@dpfaragir.com
// Email #2: b_yaghobi@yahoo.com
//
// ------------------------------------------------------------------------------
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ------------------------------------------------------------------------------
unit DPF.iOS.AVPlayer;

interface

{$I DPF.iOS.Defs.inc}

uses
  System.SysUtils,
  System.Classes,
  System.Types,
  System.Math,
  FMX.Types,
  FMX.Controls,

  DPF.iOS.BaseControl,
{$IFDEF IOS}
  iOSapi.CocoaTypes,
  Macapi.ObjCRuntime,
  Macapi.ObjectiveC,
  IOSapi.MediaPlayer,
  IOSapi.CoreMedia,
  IOSapi.AVFoundation,
  IOSapi.Foundation,
  IOSapi.CoreGraphics,
  IOSapi.UIKit,
  FMX.Platform.iOS,
  DPF.iOS.Classes,
{$ENDIF}
  FMX.Forms;

// ------------------------------------------------------------------------------
type

  TDPFAVPlayer = class;

{$IFDEF IOS}

  IAVPlayerDelegate = interface( IObjectiveC )
    ['{D52B4A66-7F1E-40B7-A770-AC95A8A1CFC0}']
    procedure itemDidFinishPlaying; cdecl;
  end;

  TDPFAVPlayerDelegate = class( TOCLocal, IAVPlayerDelegate )
  private
    FDPFAVPlayer: TDPFAVPlayer;
  public
    constructor create( ADPFAVPlayer: TDPFAVPlayer );

    procedure itemDidFinishPlaying; cdecl;
  end;

  // ------------------------------------------------------------------------------
  IDPFTimerDelegate = interface( IObjectiveC )
    ['{7850D521-4396-47E2-8354-09DAFD7C8C1F}']
    procedure ondidTimer( timer: NSTimer ); cdecl;
  end;

  // ------------------------------------------------------------------------------
  TDPFTimerDelegate = class( TOCLocal, IDPFTimerDelegate )
  private
    FDPFAVPlayer: TDPFAVPlayer;
  public
    constructor Create( ADPFAVPlayer: TDPFAVPlayer );
    procedure ondidTimer( timer: NSTimer ); cdecl;
  end;

  // ------------------------------------------------------------------------------
  AVAudioSessionDelegate = interface( IObjectiveC )
    ['{21D9508D-7491-44F4-AE69-2EDEEBA4F271}']
    procedure beginInterruption; cdecl;
    procedure endInterruption; cdecl;
    procedure endInterruptionWithFlags( flags: NSUInteger ); cdecl;
    procedure inputIsAvailableChanged( isInputAvailable: Boolean ); cdecl;
  end;

  // ------------------------------------------------------------------------------
  TDPFAVAudioSessionDelegate = class( TOCLocal, AVAudioSessionDelegate )
  private
    FDPFAVPlayer: TDPFAVPlayer;
  public
    constructor Create( ADPFAVPlayer: TDPFAVPlayer );

    procedure beginInterruption; cdecl;
    procedure endInterruption; cdecl;
    procedure endInterruptionWithFlags( flags: NSUInteger ); cdecl;
    procedure inputIsAvailableChanged( isInputAvailable: Boolean ); cdecl;
  end;

  // ------------------------------------------------------------------------------

{$ENDIF}

  // ----------------------------------------------------------------------------
  TDPFOnAudioEvent        = procedure( sender: TObject ) of object;
  TDPFOnAudioPlayingEvent = procedure( sender: TObject; currentTime: Int64; duration: int64 ) of object;

  // ----------------------------------------------------------------------------
  [ComponentPlatformsAttribute( PidWin32 or PidWin64 or pidLinux32 or pidAndroid or PidOSX32 or PidiOSDevice or PidiOSSimulator )]
  TDPFAVPlayer = class( TComponent )
  private
    { Private declarations }
{$IFDEF IOS}
    FNSTimer                  : NSTimer;
    FMPAVPlayer               : AVPlayer;
    FDPFAVPlayerDelegate      : TDPFAVPlayerDelegate;
    FDPFTimerDelegate         : TDPFTimerDelegate;
    FDPFAVAudioSessionDelegate: TDPFAVAudioSessionDelegate;
{$ENDIF}
    FOnFinishPlaying : TDPFOnAudioEvent;
    FOnPlaying       : TDPFOnAudioPlayingEvent;
    FPlayInBackground: Boolean;
  protected
    { Protected declarations }

{$IFDEF IOS}
{$ENDIF}
  public
    { Public declarations }
    constructor Create( AOwner: TComponent ); override;
    destructor Destroy; override;
    procedure Open( URL: string; isLocal: Boolean; AutoPlay: Boolean );
    procedure Close;
    procedure Play;
    procedure Stop;
    procedure Pause;
    function isPlaying: Boolean;
    function isStopped: Boolean;
    function isPaused: Boolean;
  published
    { Published declarations }
    property PlayInBackground: Boolean read FPlayInBackground write FPlayInBackground default false;
    property OnFinishPlaying : TDPFOnAudioEvent read FOnFinishPlaying write FOnFinishPlaying;
    property OnPlaying       : TDPFOnAudioPlayingEvent read FOnPlaying write FOnPlaying;
  end;

implementation

// ------------------------------------------------------------------------------
{ TDPFAVPlayer }
constructor TDPFAVPlayer.Create( AOwner: TComponent );
begin
  inherited Create( AOwner );
{$IFDEF IOS}
  FPlayInBackground          := false;
  FDPFAVPlayerDelegate       := TDPFAVPlayerDelegate.create( Self );
  FDPFTimerDelegate          := TDPFTimerDelegate.Create( Self );
  FDPFAVAudioSessionDelegate := TDPFAVAudioSessionDelegate.Create( Self );
{$ENDIF}
end;

// ------------------------------------------------------------------------------
destructor TDPFAVPlayer.Destroy;
begin
{$IFDEF IOS}
  if Assigned( FNSTimer ) then
  begin
    FNSTimer.invalidate;
    FNSTimer := nil;
  end;

  FDPFTimerDelegate.DisposeOf;
  FDPFAVPlayerDelegate.DisposeOf;
  if Assigned( FMPAVPlayer ) then
  begin
    FMPAVPlayer.Release;
    FMPAVPlayer := nil;
  end;

{$ENDIF}
  inherited;
end;

// ------------------------------------------------------------------------------
function TDPFAVPlayer.isPaused: Boolean;
begin
{$IFDEF IOS}
  result := Assigned( FMPAVPlayer ) and Assigned( FMPAVPlayer.currentItem ) and ( FMPAVPlayer.rate = 0 ) and ( round( CMTimeGetSeconds( FMPAVPlayer.currentTime ) ) > 0 );
{$ELSE}
  result := false;
{$ENDIF}
end;

// ------------------------------------------------------------------------------
function TDPFAVPlayer.isPlaying: Boolean;
begin
{$IFDEF IOS}
  result := Assigned( FMPAVPlayer ) and Assigned( FMPAVPlayer.currentItem ) and ( FMPAVPlayer.rate <> 0 );
{$ELSE}
  result := false;
{$ENDIF}
end;

// ------------------------------------------------------------------------------
function TDPFAVPlayer.isStopped: Boolean;
begin
{$IFDEF IOS}
  result := Assigned( FMPAVPlayer ) and Assigned( FMPAVPlayer.currentItem ) and ( FMPAVPlayer.rate = 0 ) and ( round( CMTimeGetSeconds( FMPAVPlayer.currentTime ) ) = 0 );
{$ELSE}
  result := false;
{$ENDIF}
end;

// ------------------------------------------------------------------------------
procedure TDPFAVPlayer.Pause;
begin
{$IFDEF IOS}
  if Assigned( FMPAVPlayer ) then
    FMPAVPlayer.pause;
{$ENDIF}
end;

// ------------------------------------------------------------------------------
procedure TDPFAVPlayer.Stop;
begin
{$IFDEF IOS}
  if Assigned( FMPAVPlayer ) then
  begin
    FMPAVPlayer.pause;
    FMPAVPlayer.seekToTime( CMTimeMake( 0, 1000000000 ) );
  end;
{$ENDIF}
end;

// ------------------------------------------------------------------------------
procedure TDPFAVPlayer.Play;
begin
{$IFDEF IOS}
  if Assigned( FMPAVPlayer ) and Assigned( FMPAVPlayer.currentItem ) then
    FMPAVPlayer.play;
{$ENDIF}
end;

// ------------------------------------------------------------------------------
procedure TDPFAVPlayer.Open( URL: string; isLocal: Boolean; AutoPlay: Boolean );
{$IFDEF IOS}
var
  FNSURL      : NSURL;
  playerItem  : AVPlayerItem;
  sessionError: NSError;
{$ENDIF}
begin
{$IFDEF IOS}
  sessionError := nil;
  if FPlayInBackground then
  begin
    TAVAudioSession.Wrap( TAVAudioSession.OCClass.sharedInstance ).setDelegate( FDPFAVAudioSessionDelegate.GetObjectID );
    TAVAudioSession.Wrap( TAVAudioSession.OCClass.sharedInstance ).setCategory( AVAudioSessionCategoryPlayback, sessionError );
    TAVAudioSession.Wrap( TAVAudioSession.OCClass.sharedInstance ).setActive( true, sessionError );
    TUIApplication.Wrap( TUIApplication.OCClass.sharedApplication ).beginReceivingRemoteControlEvents;
  end;

  if Assigned( FMPAVPlayer ) then
  begin
    Close ;
  end;

  if isLocal then
    FNSURL := TNSURL.Wrap( TNSURL.OCClass.fileURLWithPath( NSStr( URL ) ) )
  else
    FNSURL := TNSURL.Wrap( TNSURL.OCClass.URLWithString( NSStr( URL ) ) );

  playerItem  := TAVPlayerItem.Wrap( TAVPlayerItem.OCClass.playerItemWithURL( FNSURL ) );
  FMPAVPlayer := TAVPlayer.Wrap( TAVPlayer.Alloc.initWithPlayerItem( playerItem ) );

  TNSNotificationCenter.Wrap( TNSNotificationCenter.OCClass.defaultCenter ).addObserver( FDPFAVPlayerDelegate.GetObjectID, sel_getUid( 'itemDidFinishPlaying' ), ( NSSTR( 'AVPlayerItemDidPlayToEndTimeNotification' ) as ILocalObject ).GetObjectID, nil );

  if AutoPlay then
    FMPAVPlayer.play;

  if not Assigned( FNSTimer ) then
  begin
    FNSTimer := TNSTimer.Wrap( TNSTimer.OCClass.scheduledTimerWithTimeInterval( 0.1, FDPFTimerDelegate.GetObjectID, Sel_getUid( 'ondidTimer:' ), nil, true ) );
  end;

{$ENDIF}
end;

// ------------------------------------------------------------------------------
procedure TDPFAVPlayer.Close;
begin
{$IFDEF IOS}
  if Assigned( FMPAVPlayer ) then
  begin
    TNSNotificationCenter.Wrap( TNSNotificationCenter.OCClass.defaultCenter ).removeObserver( FDPFAVPlayerDelegate.GetObjectID );

    FMPAVPlayer.pause;
    FMPAVPlayer.release;
    FMPAVPlayer := nil;
  end;
{$ENDIF}
end;

// ------------------------------------------------------------------------------
{$IFDEF IOS}

{ TDPFAVPlayerDelegate }
constructor TDPFAVPlayerDelegate.create( ADPFAVPlayer: TDPFAVPlayer );
begin
  inherited create;
  FDPFAVPlayer := ADPFAVPlayer;
end;

// ------------------------------------------------------------------------------
procedure TDPFAVPlayerDelegate.itemDidFinishPlaying;
begin
  if assigned( FDPFAVPlayer.OnFinishPlaying ) then
    FDPFAVPlayer.OnFinishPlaying( FDPFAVPlayer );
end;

// ------------------------------------------------------------------------------
{ TDPFTimerDelegate }
constructor TDPFTimerDelegate.Create( ADPFAVPlayer: TDPFAVPlayer );
begin
  inherited create;
  FDPFAVPlayer := ADPFAVPlayer;
end;

// ------------------------------------------------------------------------------
procedure TDPFTimerDelegate.ondidTimer( timer: NSTimer );
var
  currentTime: int64;
  durationF  : Double;
  duration   : int64;
begin
  duration := 0;

  if assigned( FDPFAVPlayer.OnPlaying ) then
  begin
    if not Assigned( FDPFAVPlayer.FMPAVPlayer ) or not Assigned( FDPFAVPlayer.FMPAVPlayer.currentItem ) then
    begin
      FDPFAVPlayer.OnPlaying( FDPFAVPlayer, 0, 0 );
      exit;
    end;

    // currentTime := FDPFAVPlayer.FMPAVPlayer.currentTime.value div FDPFAVPlayer.FMPAVPlayer.currentTime.timescale;
    currentTime := round( CMTimeGetSeconds( FDPFAVPlayer.FMPAVPlayer.currentTime ) );
    if Assigned( FDPFAVPlayer.FMPAVPlayer.currentItem ) then
    begin
      durationF := CMTimeGetSeconds( FDPFAVPlayer.FMPAVPlayer.currentItem.duration );
      if not IsNan( durationF ) then
        duration := round( durationF );
    end;

    if FDPFAVPlayer.FMPAVPlayer.status <> 1 then
      exit;

    FDPFAVPlayer.OnPlaying( FDPFAVPlayer, currentTime, duration );
  end;
end;

// ------------------------------------------------------------------------------
{ TDPFAVAudioSessionDelegate }
procedure TDPFAVAudioSessionDelegate.beginInterruption;
begin
  { }
end;

// ------------------------------------------------------------------------------
constructor TDPFAVAudioSessionDelegate.Create( ADPFAVPlayer: TDPFAVPlayer );
begin
  inherited Create;
  FDPFAVPlayer := ADPFAVPlayer;
end;

// ------------------------------------------------------------------------------
procedure TDPFAVAudioSessionDelegate.endInterruption;
begin

end;

// ------------------------------------------------------------------------------
procedure TDPFAVAudioSessionDelegate.endInterruptionWithFlags( flags: NSUInteger );
begin

end;

// ------------------------------------------------------------------------------
procedure TDPFAVAudioSessionDelegate.inputIsAvailableChanged( isInputAvailable: Boolean );
begin

end;

// ------------------------------------------------------------------------------
{$ENDIF}

end.
