{ LCL control for playing videos using mplayer under gtk2

  Copyright (C) 2009 Mattias Gaertner mattias@freepascal.org

  This library is free software; you can redistribute it and/or modify it
  under the terms of the GNU Library General Public License as published by
  the Free Software Foundation; either version 2 of the License, or (at your
  option) any later version with the following modification:

  As a special exception, the copyright holders of this library give you
  permission to link this library with independent modules to produce an
  executable, regardless of the license terms of these independent modules,and
  to copy and distribute the resulting executable under terms of your choice,
  provided that you also meet, for each linked independent module, the terms
  and conditions of the license of that module. An independent module is a
  module which is not derived from or based on this library. If you modify
  this library, you may extend this exception to your version of the library,
  but you are not obligated to do so. If you do not wish to do so, delete this
  exception statement from your version.

  This program is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
  for more details.

  You should have received a copy of the GNU Library General Public License
  along with this library; if not, write to the Free Software Foundation,
  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

Changes:
  2014-03-24  Changes for Microsoft Windows Compatibility and added Events
              for Mouse Actions/ Michael Koecher aka six1

  2014-06-21  Added OnFeedback/OnError events
              Added OnPlay, OnStop, OnPlaying events
              Expanded Timer code to track state of Player
              Added pausing_keep_force to all commands
                - simply requesting state information was enough to resume paused video
                - adding pausing_keep was insufficent
              Added Duration, Position
              Replaced StrToCmdParam with AnsiQuotedStr in Play
                - StrToCmdParam didn't work under Windows - wrapped filename in ', Windows needed "
              Persisted FCanvas outside of IDE to prevent painting issues when no file playing
              / Mike Thompson

  2014-06-24  Added FindMPlayerPath (Refactored code from Play)
  2014-06-28  Extended FindMPlayer to also look for mplayer in a subfolder of the exe
              Fixed painting issues when playing audio files (introduces a flicker on
                resize when playing video :-( )...
              Fixed repeated requests for volume in files that don't support volme
              Changed TProcessUTF8 population code in .Play from .CommandLine to
                use .Executable & .Parameters
                - incidently removed the need to use AnsiQuotedStr around Filename under Windows
              Added Rate (Fast Forward only, mplayer doesn't support rewind)
              Only request position updates every ON_PLAYING_INTERVAL
              Set Volume on Play
              Added GrabImage and OnGrabImage (delay before mplayer grabs image)
                - doesn't work well with some renderers (-glnosw for instance,
                  also inconsistently on -vo X11)
                - Capturing failed attempts in code will be hard, for now I'll
                  just ensure this is documented on the wiki (recommend -vo direct3d under win)
              / Mike Thompson
  2014-07-01  Discovered -identify to load stats (including Start Time)
              Moved set volume on play to the parameters
              Refactored TimerEvent to ensure OnPlay & OnPlaying are broadcast in correct sequence
              Added VideoInfo and AudioInfo (load values from -identify)
              Fixed Position for videos with embedded Start_Time
              Deprecated PlayerProcess (no need for it to be exposed anymore)
              Realised no need for StepForward/StepBack - can be implemented externally via Position
              Exposed OnMouseWheel and implemented wheelmouse scrolling through video in FullFeatured
                demo
              / Mike Thompson

TODO
              EXTENSIVE TESTING UNDER LINUX
                - Tested under Linus Mint 16 (MATE) with mplayer installed (not mplayer2)
              Consider descending control from TGraphicControl (instead of creating FCanvas)

NOTES
  2014-06-29  TProcessUTF8 is a thin wrapper over TProcess.  TProcess on Windows
                is not unicode aware, so there is currently an issue playing unicode
                filenames under windows.
                No easy apparent solution other than upgrading TProcess (win\process.inc).
}
unit MPlayerCtrl;

{$mode objfpc}{$H+}

{$ifdef Linux}
 {$ifndef LCLgtk2}
 {$error this unit only supports LCL under gtk2}
 {$endif}
{$endif}

interface

uses
  Classes, SysUtils, Controls, WSLCLClasses, LCLProc, LCLType, InterfaceBase,
  LResources, LMessages, Graphics, ExtCtrls, FileUtil, Process, UTF8Process,
  LazFileUtils
  {$ifdef Linux}
  , gtk2int, gtk2, glib2, gdk2x, Gtk2WSControls, GTK2Proc, Gtk2Def
  {$endif}
  ;

type
  TVideoInfo = record
    Codec: string;
    Format: string;
    Width, Height: Integer;
    FPS: Single;
    Bitrate: Integer;
  end;

  TAudioInfo = record
    Codec: string;
    Format: string;
    Bitrate: Single;
    Channels: Integer;
    SampleRate: Integer;    // Hz
  end;

  { TCustomMPlayerControl }
  
  TOnFeedback = procedure(ASender: TObject; AStrings: TStringList) of object;
  TOnError = procedure(ASender: TObject; AStrings: TStringList) of object;
  TOnPlaying = procedure(ASender: TObject; APosition: single) of object;
  TOnGrabImage = Procedure(ASender: TObject; AFilename: String) of object;

  TCustomMPlayerControl = class(TWinControl)
  private
    FFilename: string;
    FImagePath: string;
    FLastImageFilename: string;
    FOnGrabImage: TOnGrabImage;
    FRate: single;
    FStartParam:string;
    FLoop: integer;
    FMPlayerPath: string;
    FPaused: boolean;
    FPlayerProcess: TProcessUTF8;
    FTimer: TTimer;
    FVolume: integer;
    FCanvas: TCanvas;
    FPosition: Single;
    FLastPosition: string;
    FRequestingPosition: boolean;
    FLastTimer: TDateTime;
    FRequestVolume: boolean;
    FStartTime: single;
    FDuration: single;
    FOnError: TOnError;
    FOnFeedback: TOnFeedback;
    FOnPlay: TNotifyEvent;
    FOnPlaying: TOnPlaying;
    FOnStop: TNotifyEvent;
    FOutList: TStringList;
    FVideoInfo: TVideoInfo;
    FAudioInfo: TAudioInfo;
    function GetPosition: single;
    function GetRate: single;
    procedure SetImagePath(AValue: string);
    procedure SetPosition(AValue: single);
    procedure SetFilename(const AValue: string);
    procedure SetLoop(const AValue: integer);
    procedure SetMPlayerPath(const AValue: string);
    procedure SetPaused(const AValue: boolean);
    procedure SetRate(AValue: single);
    procedure SetVolume(const AValue: integer);
    procedure SetStartParam(const AValue: string);
    procedure TimerEvent(Sender: TObject);
  protected
    procedure WMPaint(var Message: TLMPaint); message LM_PAINT;
    procedure WMSize(var Message: TLMSize); message LM_SIZE;
	
    procedure InitialiseInfo;
  public
    constructor Create(TheOwner: TComponent); override;
    destructor Destroy; override;
    procedure SendMPlayerCommand(Cmd: string); // see: mplayer -input cmdlist and http://www.mplayerhq.hu/DOCS/tech/slave.txt
    function Running: boolean;
    procedure Play;
    procedure Stop;
    function Playing: boolean;
    procedure Invalidate; override;
    procedure EraseBackground(DC: HDC); override;
  public
    function FindMPlayerPath : Boolean;

    procedure GrabImage;
    property LastImageFilename: String read FLastImageFilename;

    property Filename: string read FFilename write SetFilename;
    property StartParam: string read FStartParam write SetStartParam;
    property MPlayerPath: string read FMPlayerPath write SetMPlayerPath;
    property PlayerProcess: TProcessUTF8 read fPlayerProcess;  deprecated;
    property Paused: boolean read FPaused write SetPaused;
    property Loop: integer read FLoop write SetLoop; // -1 no, 0 forever, 1 once, 2 twice, ...
    property Volume: integer read FVolume write SetVolume;

    property ImagePath: string read FImagePath write SetImagePath;

    property Rate: single read GetRate write SetRate; // mplayer only supports 0.1 to 100
    property Duration: single read FDuration; // seconds
    property Position: single read GetPosition write SetPosition; // seconds

    property VideoInfo: TVideoInfo read FVideoInfo; // this isn't fully populated until OnPlay recieved
    property AudioInfo: TAudioInfo read FAudioInfo; // this isn't fully populated until OnPlay received

    property OnFeedback: TOnFeedback read FOnFeedback write FOnFeedback;
    property OnError: TOnError read FOnError write FOnError;
    property OnPlaying: TOnPlaying read FOnPlaying write FOnPlaying;
    property OnPlay: TNotifyEvent read FOnPlay write FOnPlay;
    property OnStop: TNotifyEvent read FOnStop write FOnStop;
    Property OnGrabImage: TOnGrabImage read FOnGrabImage write FOnGrabImage;
  end;

  TMPlayerControl = class(TCustomMPlayerControl)
  published
    property Align;
    property Anchors;
    property BorderSpacing;
    property Enabled;
    property Filename;
    property Loop;
    property OnChangeBounds;
    property OnConstrainedResize;
    property OnResize;
    property OnClick;
    property OnMouseUp;
    property OnMouseDown;
    property OnMouseWheel;
    property Visible;
    property Volume;     // 0 to 100
    property OnFeedback; // Provides standard console output from mplayer
    property OnError;    // Provides stderr console output from mplayer
    property OnPlaying;  // When not paused, an event every 250ms to 500ms with Position
    property OnPlay;     // Sent after mplayer initialises the current video file
    property OnStop;     // Sent sometime (up to approx 250ms) after mplayer finishes current video
    property OnGrabImage; // Fired when mplayer reports the filename of the image grab
  end;

  { TWSMPlayerControl }

  {$ifdef Linux}
  TWSMPlayerControl = class(TGtk2WSWinControl)
  published
    class function CreateHandle(const AWinControl: TWinControl;
                                const AParams: TCreateParams): HWND; override;
    class procedure DestroyHandle(const AWinControl: TWinControl); override;
  end;
  {$endif}

Const
  ON_PLAYING_INTERVAL = 500 / (24*60*60*1000);

procedure Register;

implementation

Uses
  Forms;

procedure Register;
begin
  RegisterComponents('Multimedia',[TMPlayerControl]);
end;

// returns the value from "ANS_PropertyName=Value" strings
function ExtractAfter(AInput, AIdentifier: string): string; inline;
begin
  AInput := Lowercase(AInput);
  AIdentifier := Lowercase(AIdentifier);

  Result := Copy(AInput, Length(AIdentifier) + 1, Length(AInput) - Length(AIdentifier));
end;

{ TCustomMPlayerControl }

procedure TCustomMPlayerControl.TimerEvent(Sender: TObject);
var
  ErrList: TStringList;
  i: integer;
  sTemp: string;
  iPosEquals, iPosAfterUS: SizeInt;
  sValue: string;
  sProperty: string;
  iError: Integer;
  bPostOnPlay, bPostOnStop, bPostOnPlaying: boolean;

begin
  bPostOnPlay:=False;
  bPostOnStop:=False;
  bPostOnPlaying:=False;
  if FPlayerProcess<>nil then
  begin
    If Running And ((Now-FLastTimer)>ON_PLAYING_INTERVAL) Then
    begin
      // Inject a request for current position
      if Assigned(FOnPlaying) and not FPaused then
      begin
        SendMPlayerCommand('get_time_pos');
        FRequestingPosition := True;
      end;

      // Inject a request for Volume level
      if FRequestVolume then
        SendMPlayerCommand('get_property volume');

      FLastTimer := Now;
      bPostOnPlaying := True;
    end;

    if FPlayerProcess.Output.NumBytesAvailable > 0 then
    begin
      FOutList.LoadFromStream(FPlayerProcess.Output);

      // Look for responses to injected commands...
      // or for standard issued information
      for i := FOutList.Count - 1 downto 0 do
      begin
        sTemp := Lowercase(FOutList[i]);
        iPosEquals := Pos('=', sTemp);

        // Identify requests look like ID_Property=Value
        // Property requests look like ANS_Property=Value
        if (iPosEquals>1) and ((Pos('ans_', sTemp)=1) or (Pos('id_', sTemp)=1)) then
        begin
          iPosAfterUS := Pos('_', sTemp)+1;
          sValue := Copy(sTemp, iPosEquals + 1, Length(sTemp) - iPosEquals);
          sProperty := Copy(sTemp, iPosAfterUS, iPosEquals - iPosAfterUS);

          if Assigned(FOnPlaying) and (FRequestingPosition) and (sProperty = 'time_position') then
          begin
            // Are we paused by any chance?
            if sValue = FLastPosition then
              SendMPlayerCommand('get_property pause');

            FLastPosition := sValue;

            FPosition := StrToFloatDef(sValue, 0) - FStartTime;

            // Don't remove any further ANS_Time_Positions, they're not ours...
            FRequestingPosition := False;

            // clear this response from the queue
            FOutList.Delete(i);
          end
          else
            case sProperty Of
              'volume' :
                begin
                  FVolume := Trunc(0.5 + StrToFloatDef(sValue, 100));
                  FRequestVolume := False;

                  // clear this response from the queue
                  FOutList.Delete(i);
                 end;
              'length'       : FDuration := StrToFloatDef(sValue, -1);
              'pause'        : FPaused := (sValue = 'yes');
              'video_codec'  : FVideoInfo.Codec:=sValue;
              'video_format' : FVideoInfo.Format:=sValue;
              'video_bitrate': FVideoInfo.Bitrate:=StrToIntDef(sValue, 0);
              'video_width'  : FVideoInfo.Width:=StrToIntDef(sValue, 0);
              'video_height' : FVideoInfo.Height:=StrToIntDef(sValue, 0);
              'video_fps'    : FVideoInfo.FPS:=StrToFloatDef(sValue, 0);
              'start_time'   : FStartTime:=StrToFloatDef(sValue, 0);
              //'seekable'     : FSeekable:=(sValue='1');
              'audio_codec'  : FAudioInfo.Codec:=sValue;
              'audio_format' : FAudioInfo.Format:=sValue;
              'audio_bitrate': FAudioInfo.Bitrate:=StrToIntDef(sValue, 0);
              'audio_rate'   : FAudioInfo.SampleRate:=StrToIntDef(sValue, 0);
              'audio_nch'    : FAudioInfo.Channels:=StrToIntDef(sValue, 0);
              'exit'         : bPostOnStop:=True;
          end;
        end // ID_ or ANS_
        else if Assigned(FOnPlay) and (sTemp = 'starting playback...') then
          bPostOnPlay:=True
        else if (Pos('*** screenshot', sTemp)=1) Then
        begin
          //  result looks like *** screenshot 'shot0002.png' ***
          FLastImageFilename:=IncludeTrailingBackslash(GetCurrentDirUTF8) + Copy(sTemp, 17, Pos('.', sTemp)-17+4);

          if assigned(FOnGrabImage) And FileExistsUTF8(FLastImageFilename) then
            FOnGrabImage(Self, FLastImageFilename);

          // clear this response from the queue
          FOutList.Delete(i);
        end
        else if sTemp='sending vfctrl_screenshot!' then
          FOutList.Delete(i);
      end;

      if Assigned(FOnFeedback) and (FOutlist.Count > 0) then
        FOnFeedback(Self, FOutlist);
    end;

    if FPlayerProcess.StdErr.NumBytesAvailable > 0 then
    begin
      ErrList := TStringList.Create;
      try
        ErrList.LoadFromStream(FPlayerProcess.Stderr);

        // Catch error retrieving volume
        If FRequestVolume Then
        begin
          iError := ErrList.IndexOf('Failed to get value of property ''volume''.');
          If iError<>-1 Then
          begin
            Errlist.Delete(iError);

            // Prevent further requests for volume
            FVolume := 0;
            FRequestVolume := False;
          end;
        end;

        if Assigned(FOnError) then
          FOnError(Self, ErrList);
      finally
        ErrList.Free;
      end;
    end;
  end;

  // don't post the OnPlay until all the data above is processed
  if Assigned(FOnPlay) and bPostOnPlay then
    FOnPlay(Self);

  If Assigned(FOnPlaying) And bPostOnPlaying then
    FOnPlaying(Self, FPosition);

  If (not Running) Or bPostOnStop Then
    Stop;
end;

procedure TCustomMPlayerControl.WMPaint(var Message: TLMPaint);
begin
  Include(FControlState, csCustomPaint);
  inherited WMPaint(Message);
  if (csDesigning in ComponentState) and (FCanvas<>nil) then begin
    with FCanvas do begin
      if Message.DC <> 0 then
        Handle := Message.DC;
      Brush.Color:=clLtGray;
      Pen.Color:=clRed;
      Rectangle(0,0,Self.Width-1,Self.Height-1);
      MoveTo(0,0);
      LineTo(Self.Width,Self.Height);
      MoveTo(0,Self.Height);
      LineTo(Self.Width,0);
      if Message.DC <> 0 then
        Handle := 0;
    end;
  end;
  Exclude(FControlState, csCustomPaint);
end;

procedure TCustomMPlayerControl.WMSize(var Message: TLMSize);
begin
  if (Message.SizeType and Size_SourceIsInterface)>0 then
    DoOnResize;
end;

procedure TCustomMPlayerControl.SetStartParam(const AValue: string);
begin
  if FStartParam=AValue then exit;
  FStartParam:=AValue;
end;

procedure TCustomMPlayerControl.SetFilename(const AValue: string);
  // Copied from win\process.inc
  // mplayer uses identical params under linux, so this is safe
  Function MaybeQuoteIfNotQuoted(Const S : String) : String;
  begin
    If (Pos(' ',S)<>0) and (pos('"',S)=0) then
      Result:='"'+S+'"'
    else
       Result:=S;
  end;
begin
  if FFilename=AValue then exit;
  FFilename:=AValue;
  if Running then
    SendMPlayerCommand('loadfile '+MaybeQuoteIfNotQuoted(Filename));
end;

procedure TCustomMPlayerControl.SetLoop(const AValue: integer);
begin
  if FLoop=AValue then exit;
  FLoop:=AValue;
  if Running then
    SendMPlayerCommand('loop '+IntToStr(FLoop));
end;

procedure TCustomMPlayerControl.SetMPlayerPath(const AValue: string);
begin
  if FMPlayerPath=AValue then exit;
  FMPlayerPath:=AValue;
end;

procedure TCustomMPlayerControl.SetPaused(const AValue: boolean);
begin
  if FPaused=AValue then exit;
  if Running then begin
    FPaused:=AValue;
    SendMPlayerCommand('pause');
  end;
end;

procedure TCustomMPlayerControl.SetRate(AValue: single);
begin
  if FRate=AValue then Exit;
  if (FRate<0.1) or (FRate>100) then Exit;
  if Running then begin
    FRate:=AValue;
    SendMPlayerCommand(Format('set_property speed %.3f', [FRate]));
  end;
end;

procedure TCustomMPlayerControl.SetVolume(const AValue: integer);
begin
  if FVolume=AValue then exit;
  FVolume:=AValue;
  if Running then
  begin
    SendMPlayerCommand('volume ' + IntToStr(FVolume) + ' 1');
    FRequestVolume := True;
  end;
end;

constructor TCustomMPlayerControl.Create(TheOwner: TComponent);
begin
  inherited Create(TheOwner);
  ControlStyle:=ControlStyle-[csSetCaption];
  FCanvas := TControlCanvas.Create;
  TControlCanvas(FCanvas).Control := Self;
  SetInitialBounds(0, 0, 160, 90);

  FOutlist := TStringList.Create;

  FMPlayerPath := 'mplayer' + GetExeExt;

  FTimer := TTimer.Create(Self);
  FTimer.Enabled := False;
  FTimer.Interval := 250;
  FTimer.OnTimer := @TimerEvent;
end;

destructor TCustomMPlayerControl.Destroy;
begin
  Stop;
  FreeAndNil(FCanvas);
  FreeAndNil(FTimer);
  FreeAndNil(FOutList);
  inherited Destroy;
end;

procedure TCustomMPlayerControl.SendMPlayerCommand(Cmd: string);
begin
  if Cmd='' then exit;
  if not Running then exit;

  if Pos('paus', Lowercase(Cmd)) <> 1 then
    Cmd := 'pausing_keep_force ' + Cmd;
  if Cmd[length(Cmd)] <> LineEnding then
    Cmd := Cmd + LineEnding;

  FPlayerProcess.Input.Write(Cmd[1], length(Cmd));
end;

function TCustomMPlayerControl.Running: boolean;
begin
  Result:=(fPlayerProcess<>nil) and fPlayerProcess.Running;
end;

function TCustomMPlayerControl.FindMPlayerPath: Boolean;
var
  ExePath: string;
  MPlayerExe: String;
begin
  result := FileExistsUTF8(FMPlayerPath);

  If not result then
  begin
    MPlayerExe:='mplayer'+GetExeExt;
    if FMPlayerPath='' then
      FMPlayerPath:=MPlayerExe;
    ExePath:=FMPlayerPath;
    // Is mplayer installed in the environment path?
    if not FilenameIsAbsolute(ExePath) then
      ExePath:=FindDefaultExecutablePath(ExePath);
    // is mplayer in a folder under the application folder?
    if Not FileExistsUTF8(ExePath) then
      ExePath := IncludeTrailingBackSlash(ExtractFileDir(Application.ExeName))+
        IncludeTrailingBackslash('mplayer') + MPlayerExe;
    // did we find it?
    if FileExistsUTF8(ExePath) then
    begin
      FMPlayerPath:=ExePath;
      result := true;
    end;
  end;
end;

procedure TCustomMPlayerControl.GrabImage;
begin
  if Running then
    SendMPlayerCommand('screenshot 0')
end;

procedure TCustomMPlayerControl.Play;
var
  CurWindowID: PtrUInt;
  slStartParams : TStringList;
begin
  if (csDesigning in ComponentState) then exit;

  if Running and Paused then begin
    Paused:=false;
    exit;
  end;

  if Playing then begin
    if FRate<>1 Then
      Rate := 1;
    exit;
  end;

  {$IFDEF Linux}
  if (not HandleAllocated) then exit;
  DebugLn(['TCustomMPlayerControl.Play ']);
  {$endif}

  if fPlayerProcess<>nil then
    FreeAndNil(fPlayerProcess);
//    raise Exception.Create('TCustomMPlayerControl.Play fPlayerProcess still exists');

  if not FindMPlayerPath then
    raise Exception.Create(MPlayerPath+' not found');

  {$IFDEF Linux}
    CurWindowID := GDK_WINDOW_XWINDOW({%H-}PGtkWidget(PtrUInt(Handle))^.window);
  {$else}
    CurWindowID := Handle;
  {$ENDIF}

  FPlayerProcess := TProcessUTF8.Create(Self);
  FPlayerProcess.Options := FPlayerProcess.Options + [poUsePipes, poNoConsole];

  // -really-quiet       : DONT USE: causes the video player to not connect to -wid.  Odd...
  // -noconfig all       : stop mplayer from reading commands from a text file
  // -zoom -fs           : Unsure:  Only perceptible difference is background drawn black not green
  // -vo direct3d        : uses Direct3D renderer (recommended under windows)
  // -vo gl_nosw         : uses OpenGL no software renderer
  FPlayerProcess.Executable:=FMPlayerPath;
  FPlayerProcess.Parameters.Add('-slave');     // allow us to control mplayer
  FPlayerProcess.Parameters.Add('-quiet');     // supress most messages
  FPlayerProcess.Parameters.Add('-identify');  // Request stats on playing file
  FPlayerProcess.Parameters.Add('-volume');    // Set initial volume
  FPlayerProcess.Parameters.Add(IntToStr(FVolume));
  FPlayerProcess.Parameters.Add('-vf');
  FPlayerProcess.Parameters.Add('screenshot'); // (with -vf) Allow frame grab

  FPlayerProcess.Parameters.Add('-wid');       // sets Window ID (display video in our control)
  FPlayerProcess.Parameters.Add(IntToStr(CurWindowID));

  // Add the user defined start params
  if (Trim(FStartParam)<>'') then
  begin
    slStartParams := TStringList.Create;
    try
      CommandToList(StartParam, slStartParams);
      FPlayerProcess.Parameters.AddStrings(slStartParams);
    finally
      slStartParams.Free;
    end;
  end;

  FPlayerProcess.Parameters.Add(FFilename);

  FPlayerProcess.Parameters.Delimiter:=' ';
  DebugLn(['TCustomMPlayerControl.Play ', FPlayerProcess.Parameters.DelimitedText]);

  // Normally I'd be careful to only use FOutList in the
  // Timer event, but here I'm confident the timer isn't running...
  if assigned(FOnFeedback) then
  begin
    FOutlist.Clear;
    FOutlist.Add(FPlayerProcess.Executable + ' ' + FPlayerProcess.Parameters.DelimitedText);
    FOutlist.Add('');
    FonFeedback(Self, FOutlist);
  end;

  // Populate defaults
  InitialiseInfo;

  FPlayerProcess.Execute;

  // Start the timer that handles feedback from mplayer
  FTimer.Enabled := True;
end;

procedure TCustomMPlayerControl.Stop;
begin
  if FPlayerProcess = nil then
    exit;

  DebugLn(Format('ExitStatus=%d', [fPlayerProcess.ExitStatus]));
  FPaused := False;
  FDuration := -1;
  FTimer.Enabled := False;

  SendMPlayerCommand('quit');

  FreeAndNil(FPlayerProcess);

  if Assigned(FOnStop) then
    FOnStop(Self);

  // repaint the control
  Refresh;
end;

function TCustomMPlayerControl.Playing: boolean;
begin
  Result := Running and (not Paused);
end;

procedure TCustomMPlayerControl.Invalidate;
begin
  if csCustomPaint in FControlState then exit;
  inherited Invalidate;
end;

procedure TCustomMPlayerControl.EraseBackground(DC: HDC);
begin
  if (FCanvas <> nil) then
    with FCanvas do
    begin
      if DC <> 0 then
        Handle := DC;
      Brush.Color := clLtGray;
      Rectangle(0, 0, Self.Width, Self.Height);
      if DC <> 0 then
        Handle := 0;
    end;
end;

procedure TCustomMPlayerControl.InitialiseInfo;
begin
  FLastPosition := '';
  FPosition := 0;
  FRequestVolume := False;
  FStartTime := 0;
  FRate := 1;
  FDuration := -1;

  with FVideoInfo Do
  begin
    Format := '';
    Width := 0;
    Height := 0;
    FPS := 0;
    Bitrate := 0;
  end;

  With FAudioInfo Do
  begin
    Format := '';
    Bitrate := 0;
  end;
end;

function TCustomMPlayerControl.GetPosition: single;
begin
  DebugLn(Format('Get Position %.3f', [FPosition]));
  Result := FPosition;
end;

function TCustomMPlayerControl.GetRate: single;
begin
  Result := FRate;

  //If not Running Then
  //  Result := FRate
  //Else
  //  Result := StrToFloatDef(DoCommand('get_property speed', 'ans_speed='), 1)
end;

procedure TCustomMPlayerControl.SetImagePath(AValue: string);
begin
  if DirectoryExistsUTF8(AValue) then
  begin
    FImagePath:=AValue;
    SetCurrentDirUTF8(AValue);
  end;
end;

procedure TCustomMPlayerControl.SetPosition(AValue: single);
begin
  if Running then
  begin
    if AValue>0 Then
      FPosition := AValue
    Else
      FPosition := 0;

    DebugLn(Format('Set Position to  %.3f', [FPosition]));
    SendMPlayerCommand(Format('pausing_keep seek %.3f 2', [FPosition]));
  end;
end;

{$ifdef Linux}
function MPLayerWidgetDestroyCB(Widget: PGtkWidget; {%H-}data: gPointer): GBoolean; cdecl;
begin
  FreeWidgetInfo(Widget); // created in TWSMPlayerControl.CreateHandle
  Result:=false;
end;

{ TWSMPlayerControl }

class function TWSMPlayerControl.CreateHandle(const AWinControl: TWinControl;
  const AParams: TCreateParams): HWND;
var
  NewWidget: PGtkWidget;
  WidgetInfo: PWidgetInfo;
  Allocation: TGTKAllocation;
begin
  if csDesigning in AWinControl.ComponentState then
    Result:=inherited CreateHandle(AWinControl,AParams)
  else begin
    NewWidget:=gtk_event_box_new;

    WidgetInfo := GetWidgetInfo(NewWidget,true); // destroyed in MPLayerWidgetDestroyCB
    WidgetInfo^.LCLObject := AWinControl;
    WidgetInfo^.Style := AParams.Style;
    WidgetInfo^.ExStyle := AParams.ExStyle;
    WidgetInfo^.WndProc := {%H-}PtrUInt(AParams.WindowClass.lpfnWndProc);

    // set allocation
    Allocation.X := AParams.X;
    Allocation.Y := AParams.Y;
    Allocation.Width := AParams.Width;
    Allocation.Height := AParams.Height;
    gtk_widget_size_allocate(NewWidget, @Allocation);

    if csDesigning in AWinControl.ComponentState then begin
      // at designtime setup normal handlers
      TGtk2WidgetSet(WidgetSet).FinishCreateHandle(AWinControl,NewWidget,AParams);
    end else begin
      // at runtime
      g_signal_connect(GPointer(NewWidget), 'destroy',
                       TGTKSignalFunc(@MPLayerWidgetDestroyCB), WidgetInfo);
    end;
    Result:=HWND({%H-}PtrUInt(Pointer(NewWidget)));
    DebugLn(['TWSMPlayerControl.CreateHandle ',dbgs(NewWidget)]);
  end;
end;

class procedure TWSMPlayerControl.DestroyHandle(const AWinControl: TWinControl
  );
begin
  inherited DestroyHandle(AWinControl);
end;
{$endif}

initialization
  {$ifdef Linux}
  RegisterWSComponent(TCustomMPlayerControl,TWSMPlayerControl);
  {$endif}

  {$I mplayerctrl.lrs}
end.