
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3324 8e941d3f-bd1b-0410-a28a-d453659cc2b4
876 lines
27 KiB
ObjectPascal
876 lines
27 KiB
ObjectPascal
{ 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.
|
|
|