Jump to content

CHackbart

Members
  • Content Count

    17
  • Joined

  • Last visited

Community Reputation

12 Good

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. Hi, I started create a simple media player based on the Windows Media Foundation. I have to deal with DRM encrypted channels so there is unluckily no way around. Did somebody have some experience with the Windows Media Foundation and DRM? The attached demo is a player using WMF and Firemonkey. The header files used in this project can be found here: https://github.com/FactoryXCode/MfPack You should be able to play streams (based on dash and HLS) as well as media files located on the local file system. It partially crashes here on my vm inside the the d3d11.dll. I did not tested it on a native windows system yet. The player also uses some "technique" to render firemonkey elements on top of the video. To do so you just have to put them as child object to the player. Christian fmxwmf.zip
  2. CHackbart

    MacOS NSVisualEffectView implementation

    I will create a small GitHub repo with those helpers. Especially the Z-Order handling is something I barely miss under MacOS. By removing the contentView and adding it after the other views have been added to the MainView you can easily put the FMX layer to the top.
  3. CHackbart

    MacOS NSVisualEffectView implementation

    Yes, and it works surprisingly well. You just have to execute AppendToForm with your Formular and if you do not want to have a border - since FireMonkey still is unable to render the toolbar with a style under osx - you can set this also to borderless. function AppendToForm(const AForm: TForm;const AOpacity: single=1.0;const ABorderLess: Boolean=False): Boolean; var LNSWin: NSWindow; LBlurView: NSVisualEffectView; LView: NSView; LContext: NSView; begin if not TOSVersion.Check(10,10) then begin result := false; exit; end; AForm.Fill.Kind := TBrushKind.Solid; AForm.Fill.Color := 0; LNSWin := WindowHandleToPlatform(AForm.Handle).Wnd; LContext := WindowHandleToPlatform(AForm.Handle).View; LNSWin.setOpaque(false); LNSWin.setAlphaValue(AOpacity); LBlurView := TNSVisualEffectView.Wrap( TNSVisualEffectView.Alloc.initWithFrame( MakeNSRect(0,0, AForm.Width, AForm.Height))); LBlurView.setWantsLayer(true); LBlurView.setBlendingMode(NSVisualEffectBlendingModeBehindWindow); LBlurView.setMaterial(NSVisualEffectViewMaterialFullScreenUI); LBlurView.setState(NSVisualEffectStateActive); LBlurView.setAutoresizingMask(NSViewWidthSizable or NSViewHeightSizable); LView := TNSView.Wrap(LNSWin.contentView); if ABorderLess then begin LNSWin.setStyleMask(NSBorderlessWindowMask or NSResizableWindowMask); LNSWin.setBackgroundColor(TNSColor.Wrap(TNSColor.OCClass.clearColor)); LView.setWantsLayer(true); LView.layer.setMasksToBounds(true); LView.layer.SetCornerRadius(10.0); end; LContext.removeFromSuperview; LView.addSubview(LBlurView); LView.addSubview(LContext); if GlobalUseMetal then WindowHandleToPlatform(AForm.Handle).MTView.layer.setOpaque(false); result := true; end;
  4. CHackbart

    MacOS NSVisualEffectView implementation

    I managed to get it working. To do so I had to move the main view to the front and set the background color of the layer to transparent. This works also when UseGlobalMetal is enabled.
  5. Hi, I currently try to write a fft visualizer (without any third party), but the last time I heard from dft/fft is about 20 years ago. This is what I did from scratch, but the implementation does not seem to be correct. Maybe you have an idea? unit umain; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, bgrabitmap, BGRABitmapTypes, uacinerella; type TSpectrum = (tspWaveform, tspFFT, tspLogarithmic, tsp3d); TComplex = record re, im: double; end; TComplexArray = array of TComplex; TDoubleArray = array of double; { TForm1 } TForm1 = class(TForm) Image1: TImage; procedure FormActivate(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private FInstance: PAc_instance; FInput: TFileStream; FAudiodecoder: PAc_decoder; FBitmap: TBGRABitmap; FSpectrumPalette: array [0 .. 255] of TBGRAPixel; FSpectrum: TSpectrum; FSpectrumPos: integer; FHanningWindow: TComplexArray; function ReadProc(Buf: PByte; Size: integer): integer; procedure ProcessAudio(Buf: PByte; Size: integer); procedure ProcessData(); procedure CreatePalette; public end; var Form1: TForm1; implementation uses Math; {$R *.lfm} function read_proc(Sender: Pointer; buf: PByte; size: integer): integer; cdecl; begin Result := TForm1(Sender).ReadProc(buf, size); end; function cInit(const re, im: double): TComplex; begin Result.re := re; Result.im := im; end; function cadd(const a, b: TComplex): TComplex; begin Result.re := a.re + b.re; Result.im := a.im + b.im; end; function csub(const a, b: TComplex): TComplex; begin Result.re := a.re - b.re; Result.im := a.im - b.im; end; function cmul(const a, b: TComplex): TComplex; begin Result.re := a.re * b.re - a.im * b.im; Result.im := b.re * a.im + a.re * b.im; end; function cabs(const a: TComplex): double; begin Result := sqrt(a.re * a.re + a.im * a.im); end; function getBit(const Val: DWord; const BitVal: byte): boolean; begin Result := (Val and (1 shl BitVal)) <> 0; end; function enableBit(const Val: DWord; const BitVal: byte; const SetOn: boolean): DWord; begin Result := (Val or Int64(1 shl BitVal)) xor (integer(not SetOn) shl BitVal); end; function mirrorTransform(n, m: integer): integer; var i: integer; begin Result := 0; for i := 0 to m - 1 do Result := enableBit(Result, m - 1 - i, getBit(n, i)); end; function doPermutation(Source: TComplexArray; m: integer): TComplexArray; var i, n: integer; begin n := length(Source); SetLength(Result, n); for i := 0 to n - 1 do begin Result[i] := Source[mirrorTransform(i, m)]; end; end; function doStep(k, M: longint; prev: TComplexArray): TComplexArray; var expTerm, substractTerm: TComplex; q, j, offset: longint; begin offset := system.round(intpower(2, M - k)); SetLength(Result, length(prev)); for q := 0 to system.round(intpower(2, k - 1) - 1) do begin // For each block of the matrix for j := 0 to (offset - 1) do begin // Fo each line of this block // First half Result[q * 2 * offset + j] := cadd(prev[q * 2 * offset + j], prev[q * 2 * offset + j + offset]); // Second half expTerm.re := cos((j * PI) / offset); expTerm.im := sin((j * PI) / offset); substractTerm := csub(prev[q * 2 * offset + j], prev[q * 2 * offset + j + offset]); Result[q * 2 * offset + j + offset] := cmul(expTerm, substractTerm); end; end; end; function doFFT(g: TComplexArray; order: integer): TComplexArray; var previousRank, nextRank: TComplexArray; i: integer; begin previousRank := g; for i := 1 to order do begin nextRank := doStep(i, order, previousRank); previousRank := nextRank; end; Result := doPermutation(nextRank, order); end; function WindowHann(const ACount: integer): TComplexArray; var i: integer; begin SetLength(Result, ACount); for i := 0 to ACount - 1 do Result[i] := cInit(0.5 * (1.0 - cos((2.0 * PI * i) / ACount)), 0); end; function CalculateSpectrum(const ASignalWindow, AData: TComplexArray): TDoubleArray; var dftResult, tempData: TComplexArray; len: integer; i: integer; begin len := length(ASignalWindow); SetLength(tempData, len); for i := 0 to Len - 1 do begin if i < len then tempData[i] := cmul(AData[i], ASignalWindow[i]) else tempData[i] := cInit(0, 0); end; dftResult := doFFT(tempData, 10); Setlength(result, Length(dftResult)); for i := 0 to length(result) - 1 do result[i] := cabs(dftResult[i]); end; { TForm1 } procedure TForm1.FormCreate(Sender: TObject); var i: integer; info: TAc_stream_info; begin FAudiodecoder := nil; FInput := TFileStream.Create('Tones_100_20000_incrementing.ac4.trp', fmOpenRead); FInstance := ac_init(); ac_open(FInstance, self, nil, @read_proc, nil, nil, nil); for i := 0 to FInstance^.stream_count - 1 do begin ac_get_stream_info(FInstance, i, @info); if info.stream_type = AC_STREAM_TYPE_AUDIO then begin FAudiodecoder := ac_create_decoder(FInstance, i); break; end; end; FBitmap := TBGRABitmap.Create; FBitmap.SetSize(640, 480); CreatePalette; FSpectrum := TSpectrum.tspLogarithmic; FHanningWindow := WindowHann(1024); end; procedure TForm1.FormActivate(Sender: TObject); begin while not application.terminated do begin ProcessData(); application.ProcessMessages; end; end; procedure TForm1.FormDestroy(Sender: TObject); begin ac_free_decoder(FAudiodecoder); ac_close(FInstance); FInput.Free; FBitmap.Free; end; procedure TForm1.CreatePalette; var i: integer; begin for i := 1 to 127 do with FSpectrumPalette[i] do begin Alpha := 255; Blue := 0; Green := 256 - 2 * i; Red := 2 * i; end; for i := 0 to 31 do begin with FSpectrumPalette[128 + i] do begin Alpha := 255; Red := 0; Green := 0; Blue := 8 * i; end; with FSpectrumPalette[128 + 32 + i] do begin Alpha := 255; Red := 8 * i; Green := 0; Blue := 0; end; with FSpectrumPalette[128 + 64 + i] do begin Alpha := 255; Red := 255; Green := 8 * i; Blue := 8 * (31 - i); end; with FSpectrumPalette[128 + 96 + i] do begin Alpha := 255; Red := 255; Green := 255; Blue := 8 * i; end; end; end; function TForm1.ReadProc(Buf: PByte; Size: integer): integer; begin Result := FInput.Read(Buf^, Size); end; procedure TForm1.ProcessData; var pack: PAc_package; begin if not assigned(FAudioDecoder) then exit; pack := ac_read_package(FInstance); try if (pack = nil) or (pack^.stream_index <> FAudiodecoder^.stream_index) then exit; if (ac_decode_package(pack, FAudiodecoder) > 0) then ProcessAudio(FAudiodecoder^.buffer, FAudiodecoder^.buffer_size); finally ac_free_package(pack); end; end; procedure TForm1.ProcessAudio(Buf: PByte; Size: integer); const BANDS = 28; var X, Y, Y1, V, B0, B1, SC: integer; Sum: single; i, z, w: integer; Step: integer; ptr: PSmallInt; signal: TComplexArray; fft: array of double; function FixRange(const Y: integer): integer; begin Result := Y * 127 div FBitmap.Height; end; begin if Size = 0 then exit; if FSpectrum <> tsp3d then FBitmap.FillTransparent; Y := 0; X := 0; if FSpectrum <> tspWaveForm then begin ptr := PSmallInt(Buf); Step := (Size div 4) div Length(FHanningWindow); if Step < 1 then Step := 1; SetLength(signal, length(FHanningWindow)); for i := 0 to high(signal) do begin signal[i].re := (32768-ptr^)/65536; signal[i].im := 0; Inc(ptr, step); end; fft := CalculateSpectrum(FHanningWindow, signal); end; Step := (Size div 4) div FBitmap.Width; if Step < 1 then Step := 1; ptr := PSmallInt(Buf); case FSpectrum of tspWaveform: begin for x := 0 to FBitmap.Width - 1 do begin V := (32767 - ptr^) * FBitmap.Height div 65536; Inc(ptr, step); if X = 0 then Y := V; repeat if Y < V then Inc(Y) else if Y > V then Dec(Y); FBitmap.SetPixel(X, Y, FSpectrumPalette[FixRange(abs(Y - FBitmap.Height div 2) * 2 + 1)]); until Y = V; end; end; TSpectrum.tspFFT: // "normal" FFT begin Y1 := 0; for X := 0 to (FBitmap.Width div 2) - 1 do begin Y := Trunc(sqrt(fft[X + 1]) * 3 * FBitmap.Height - 4); if Y > FBitmap.Height then Y := FBitmap.Height; // cap it Y1 := (Y + Y1) div 2; if (X > 0) and (Y1 > 0) then // interpolate from previous to make the display smoother while (Y1 >= 0) do begin FBitmap.SetPixel(X * 2 - 1, FBitmap.Height - Y1 - 1, FSpectrumPalette[FixRange(Y1 + 1)]); Dec(Y1); end; Y1 := Y; while (Y >= 0) do begin FBitmap.SetPixel(X * 2, FBitmap.Height - Y - 1, FSpectrumPalette[FixRange(Y + 1)]); // draw level Dec(Y); end; end; end; TSpectrum.tspLogarithmic: // logarithmic, acumulate & average bins begin B0 := 0; for X := 0 to BANDS - 1 do begin Sum := 0; B1 := Trunc(Power(2, X * 10.0 / (BANDS - 1))); if B1 > 1023 then B1 := 1023; if B1 <= B0 then B1 := B0 + 1; // make sure it uses at least 1 FFT bin SC := 10 + B1 - B0; while B0 < B1 do begin Sum := Sum + fft[1 + B0]; Inc(B0); end; Y := Trunc((sqrt(Sum / log10(SC)) * 1.7 * FBitmap.Height) - 4); // scale it if Y > FBitmap.Height then Y := FBitmap.Height; // cap it while (Y >= 0) do begin w := Trunc(0.9 * (FBitmap.Width / BANDS)); for z := 0 to w - 1 do FBitmap.SetPixel(X * (FBitmap.Width div BANDS) + z, FBitmap.Height - Y - 1, FSpectrumPalette[FixRange(Y + 1)]); Dec(Y); end; end; end; TSpectrum.tsp3d: // "3D" begin for X := 0 to FBitmap.Height - 1 do begin Y := Trunc(sqrt(fft[X + 1]) * 3 * FBitmap.Height); // scale it (sqrt to make low values more visible) if Y > FBitmap.Height then Y := FBitmap.Height; // cap it if Y < 0 then Y := 0; if (FSpectrumPos < FBitmap.Width) and (X < FBitmap.Height) then FBitmap.SetPixel(FSpectrumPos, X, FSpectrumPalette[128 + FixRange(Y)]); // plot it end; // move marker onto next position FSpectrumPos := (FSpectrumPos + 1) mod FBitmap.Width; for X := 0 to FBitmap.Height - 1 do if (FSpectrumPos < FBitmap.Width) and (X < FBitmap.Height) then FBitmap.SetPixel(FSpectrumPos, X, FSpectrumPalette[255]); end; end; Image1.Picture.Assign(FBitmap); end; end. My guess is that the way I fill the complex array with the signal data is not correct.
  6. Hi, I wrote a small solution to add this fancy semi transparent background effect supported since a while on MacOS. What I did is to translate the NSVisualEffectView class to Delphi and to write some small code which adds this view to the ContentView. First I thought it works fine, but it does not resize automatically and technically it should be moved to the bottom of the rendered subview, otherwise the view used for the FireMonkey controls is not visible. Maybe somebody has an idea how to put this view at the bottom of the view list rendered without adding this code in to the FMX.Platform.Mac. I dislike the idea to mess with the regular FMX units around. visualeffectview.zip
  7. CHackbart

    Borderstyle and Menus under OSX

    Hi, I'm somehow a bit confused about the border style property and the tmenuitem implementation on the Mac. I created a simple plain app with the free calypso style for Mac and Windows. Whilst the border is styled correct under windows it is not under OSX. I tried switching the border.style Boolean on runtime and it affects the form under windows like it should, but on the Mac it does not change anything. I do see the style objects defined in the style book. For the Mac it is named "macborderstyle". However procedure TWindowBorderCocoa.Paint(Ctx: CGContextRef); is never called. Which seem to be executed in frameDrawRect under FMX.Platform.Mac, but this function is also not executed. The second issue which confuses me is the shortcuts. They should look like this: But they simply do not show the special characters for Command, Option and Control. Does someone has an idea? I currently use Delphi 11 with the latest Update from GetIt. Christian
  8. CHackbart

    Firemonkey draw over native elements

    Yes of course, I just added the player to explain why I need this in particular. By reading the platform implementation for MacOS I found a solution which should work fine on all platforms: The public SetBoundsF function which is part of the TCommonCustomForm is executed every time the window moved. procedure SetBoundsF(const ALeft, ATop, AWidth, AHeight: Single); override; Christian
  9. CHackbart

    Firemonkey draw over native elements

    Thanks for the link, but this only works on windows. In this particular case I need it on almost all platforms, starting on a Mac. Frankly using a Timer is not the worst thing, but I hate the idea of polling on a high frequency as well as the fact that even a 1ms interval would have a delay while moving the main window and updating the content.
  10. CHackbart

    Firemonkey draw over native elements

    Hi, since this is not working properly since the beginning I thought I try to do the following. A TLayout component which helds a transparent borderless TForm. All the layout children are rendered in the form on run time, while the native control behind stays behind. This works fine, but I got stuck in the issue that if I resize the main form, the position of the transparent form won't be updated. I could solve this by a timer which checks the position in the background but this is not the best idea I guess. I added a small example which plays a video stream and draws a icon over the stream. Maybe somebody has an idea how to achieve this without a timer. Christian example.zip
  11. CHackbart

    Draw over a native element

    I am not quite happy with the solution, but it works on iOS and MacOS. Android is missing, during the lack of a working device here at the moment, maybe somebody would like to add the Android native Image handling into. The idea is to have a TLayout descendant which renders the Firemonkey content into a bitmap and draws this to a native Image View. What I have so far is the following: unit UImageLayout; interface uses System.Types, System.Classes, FMX.Layouts, FMX.Types {$IFDEF MACOS} {$IFDEF IOS} , iOSApi.UIKit {$ELSE} , Macapi.AppKit {$ENDIF} {$ENDIF}; type TImageLayout = class(TLayout) private FActive: Boolean; FLastUpdate: UInt64; {$IFDEF MACOS} {$IFDEF IOS} FView: UIImageView; {$ELSE} FView: NSImageView; {$ENDIF} {$ENDIF} procedure SetActive(AValue: Boolean); protected procedure AfterPaint; override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; property Active: Boolean read FActive write SetActive; end; implementation uses System.SysUtils, FMX.Graphics, FMX.Forms, System.IOUtils {$IFDEF MACOS} {$IFDEF IOS} , Macapi.Helpers, iOSApi.Foundation, FMX.Platform.iOS {$ELSE} , Macapi.CocoaTypes, FMX.Platform.Mac, FMX.Helpers.Mac {$ENDIF} {$ENDIF}; {$IFDEF MACOS} {$IFDEF IOS} function BitmapToUIImage(const Bitmap: TBitmap): UIImage; var mem: TMemoryStream; Data: NSData; TmpImage: UIImage; AutoreleasePool: NSAutoreleasePool; begin if Bitmap <> nil then begin Data := nil; TmpImage := nil; AutoreleasePool := TNSAutoreleasePool.Create; try mem := TMemoryStream.Create; try Bitmap.SaveToStream(mem); Data := TNSData.Wrap(TNSData.OCClass.dataWithBytes(mem.Memory, mem.size)); TmpImage := TUIImage.Alloc; Result := TUIImage.Wrap(TmpImage.initWithData(Data)); finally mem.Free; end; finally AutoreleasePool.release; end; end; end; {$ENDIF} function MakeNSRect(const ox, oy, sx, sy: Single): NSRect; begin Result.origin.x := ox; Result.origin.y := oy; Result.size.width := sx; Result.size.height := sy; end; {$ENDIF} function GetParentForm(Control: TFmxObject): TCommonCustomForm; begin if (Control.Root <> nil) and (Control.Root.GetObject is TCommonCustomForm) then Result := TCommonCustomForm(Control.Root.GetObject) else Result := nil; end; { TImageLayout } constructor TImageLayout.Create(AOwner: TComponent); begin inherited; end; destructor TImageLayout.Destroy; begin Active := false; inherited; end; procedure TImageLayout.AfterPaint; var LBitmap: TBitmap; {$IFDEF MACOS} LBounds: TRectF; Form: TCommonCustomForm; {$ENDIF} begin inherited; if (not Active) then exit; if TThread.GetTickCount64 - FLastUpdate < 100 then exit; FLastUpdate := TThread.GetTickCount64; {$IFDEF MACOS} if assigned(FView) then begin LBounds := GetAbsoluteRect; Form := GetParentForm(self); if assigned(Form) then FView.setFrame(MakeNSRect(LBounds.Left, Form.ClientHeight - LBounds.Bottom, LBounds.width, LBounds.height)); end; {$ENDIF} LBitmap := TBitmap.Create(Trunc(width), Trunc(height)); try if LBitmap.Canvas.BeginScene then begin LBitmap.Canvas.Clear(0); PaintTo(LBitmap.Canvas, LBitmap.BoundsF); LBitmap.Canvas.EndScene; //LBitmap.SaveToFile(TPath.Combine(TPath.GetDocumentsPath, 'test.png')); {$IFDEF MACOS} {$IFDEF IOS} FView.setAlpha(AbsoluteOpacity); FView.setImage(BitmapToUIImage(LBitmap)); {$ELSE} FView.setAlphaValue(AbsoluteOpacity); FView.setImage(BitmapToMacBitmap(LBitmap)); {$ENDIF} {$ENDIF} end; finally LBitmap.Free; end; end; procedure TImageLayout.SetActive(AValue: Boolean); {$IFDEF MACOS} var LBounds: TRectF; Form: TCommonCustomForm; {$IFDEF IOS} FormView: UIView; {$ELSE} FormView: NSView; {$ENDIF} {$ENDIF} begin if AValue <> Active then begin {$IFDEF MACOS} LBounds := AbsoluteRect; Form := GetParentForm(self); if (AValue) and (not assigned(FView)) and (assigned(Form)) then begin {$IFDEF IOS} FView := TUIImageView.Wrap(TUIImageView.Alloc.initWithFrame (MakeNSRect(LBounds.Left, Form.ClientHeight - LBounds.Bottom, LBounds.width, LBounds.height))); FView.retain; FormView := TUIView.Wrap (NSObjectToID(WindowHandleToPlatform(Form.Handle).View)); FormView.addSubview(FView); {$ELSE} FView := TNSImageView.Wrap(TNSImageView.Alloc.initWithFrame (MakeNSRect(LBounds.Left, Form.ClientHeight - LBounds.Bottom, LBounds.width, LBounds.height))); FView.retain; FView.setWantsLayer(true); FormView := TNSView.Wrap(WindowHandleToPlatform(Form.Handle) .Wnd.contentView); FormView.addSubview(FView, NSWindowAbove, nil); {$ENDIF} end; if assigned(FView) then FView.setHidden(not AValue) else AValue := false; {$ENDIF} FActive := AValue; end; end; end.
  12. CHackbart

    Draw over a native element

    Hi, i am wondering if there is an elegant way to draw over a native control like the Media player or Webbrowser under mobile platforms resp. MacOS. In my case I have to use AVPlayerItemVideoOutput because the content is DRM protected and I can not get the surface texture in order to paint it to the screen, embedded in Firemonkey controls. My solution is to render the FireMonkey output into a bitmap and to draw it on a NSImageView Layer which is on top. This is slow as hell and I wonder how it can be achieved in a better way than using an image. Christian
  13. CHackbart

    MacOS AVPlayer and DRM

    Sure, sorry for the delay - I just forgot to post it here. You have to update the FMX.Media.Mac resp. IOS in the following way: constructor TMacMedia.Create(const AFileName: string); var LURL: NSUrl; LAbsoluteFileName: string; LAsset: AVURLAsset; begin inherited Create(AFileName); AVMediaTypeAudio; // Force load the framework if FileExists(FileName) then begin if ExtractFilePath(FileName).IsEmpty then LAbsoluteFileName := TPath.Combine(TPath.GetHomePath, FileName) else LAbsoluteFileName := FileName; LURL := TNSUrl.Wrap(TNSUrl.OCClass.fileURLWithPath(StrToNSStr(LAbsoluteFileName))); end else LURL := StrToNSUrl(FileName); if LURL = nil then raise EFileNotFoundException.Create(SSpecifiedFileNotFound); FPixelBufferBitmap := TBitmap.Create; LAsset := TAVURLAsset.Wrap(TAVURLAsset.OCClass.URLAssetWithURL(LURL, nil)); if LAsset.hasProtectedContent then ContentKeyManager.addContentKeyRecipient(LAsset); FPlayerItem := TAVPlayerItem.Wrap(TAVPlayerItem.OCClass.playerItemWithAsset(LAsset)); FPlayerItem.retain; FPlayer := TAVPlayer.Wrap(TAVPlayer.OCClass.playerWithPlayerItem(FPlayerItem)); FPlayer.retain; FPlayerLayer := TAVPlayerLayer.Wrap(TAVPlayerLayer.OCClass.playerLayerWithPlayer(FPlayer)); FPlayerLayer.retain; FPlayerLayer.setVideoGravity(CocoaNSStringConst(libAVFoundation, 'AVLayerVideoGravityResizeAspectFill')); FPlayerLayer.setAutoresizingMask(kCALayerWidthSizable or kCALayerHeightSizable); FVideoView := TNSView.Create; FVideoView.retain; FVideoView.setWantsLayer(True); FVideoView.layer.addSublayer(FPlayerLayer); SetupVideoOutput; end; The ContentKeyManager needs two callbacks which could look like this: procedure TfrmMain.DoGetCertificate(Sender: TObject; ACert: TMemoryStream); var http: THTTPClient; begin http := THTTPClient.create; try http.ContentType := 'application/octet-stream'; http.Get(FAIRLPLAY_SERVER_URL, ACert); finally http.Free; end; end; procedure TfrmMain.DoRequestContentKey(Sender: TObject; ARequest, AResponse: TMemoryStream); var http: THTTPClient; begin http := THTTPClient.create; try ARequest.Position := 0; http.ContentType := 'application/octet-stream'; http.Post(LS_SERVER_URL, ARequest, AResponse); finally http.Free; end; end; First Fairplay asks for the certificate and returns a request to the license server which should be send as post command. In return it delivers some binary data which is the key to decode the stream. I made an "example" screenshot with a test server here. The annoying thing is that DRM content does not allow to play the video in a texture. If you start to use copyPixelBufferForItemTime in order to get the video content audio and video playback stops (without any notification). I was thinking about how to put some alpha blended NSView or UIView on top which contain the buttons and other things. One of my projects involves a media player on Android, iOS and MacOS (using Metal). It heavily uses Metal and OpenGL for the output and the video view is drawn over it (using regular firemonkey controls). Maybe you have an idea? By default the video is shown on the top right and some ui is drawn above. In fullscreen the video is in the background and the rest of the context is shown in front. You can imagine what happen if DRM is involved. You only see the image, but the ui is hidden. Christian UFairplay.pas.zip
  14. CHackbart

    MacOS AVPlayer and DRM

    Thanks, the whole translation from this: func requestApplicationCertificate() throws -> Data { print("requestApplicationCertificate called") // MARK: ADAPT - You must implement this method to retrieve your FPS application certificate. var certificateData: Data? = nil let request = NSMutableURLRequest(url: URL(string: DNSRestServices.DNS_FAIRLPLAY_SERVER_URL)!) request.httpMethod = "POST" request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type") let semaphore = DispatchSemaphore(value: 0) URLSession.shared.dataTask(with: request as URLRequest) { (responseData, _, error) -> Void in certificateData = responseData semaphore.signal() }.resume() semaphore.wait(timeout: .distantFuture) guard certificateData != nil else { throw ProgramError.missingApplicationCertificate } return certificateData! } func requestContentKeyFromKeySecurityModule(spcData: Data, assetID: String) throws -> Data { var ckcData: Data? = nil var licenseURL : String? licenseURL = DispatchQueue.main.sync { var licenseURLForSelectedChannel = "" if let keyWindow = UIWindow.key { let menuViewController = keyWindow.rootViewController as! MenuSplitViewController let playerController = menuViewController.viewControllers[1] as! DNSPlayerViewController licenseURLForSelectedChannel = playerController.licenseURLForSelectedChannel() ?? "" } return licenseURLForSelectedChannel } guard licenseURL != nil else { throw ProgramError.missingLicenseURL } guard let url = URL(string: licenseURLString) else { print("Error! Invalid URL!") //Do something else throw ProgramError.missingLicenseURL } let request = NSMutableURLRequest(url: url) request.httpMethod = "POST" request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData request.httpBody = spcData let postLength:NSString = NSString(data: spcData, encoding:String.Encoding.ascii.rawValue)! request.setValue(String(postLength.length), forHTTPHeaderField: "Content-Length") request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type") let semaphore = DispatchSemaphore(value: 0) URLSession.shared.dataTask(with: request as URLRequest) { (responseData, _, error) -> Void in ckcData = responseData semaphore.signal() }.resume() semaphore.wait(timeout: .distantFuture) guard ckcData != nil else { throw ProgramError.noCKCReturnedByKSM } return ckcData! } func handleStreamingContentKeyRequest(keyRequest: AVContentKeyRequest) { guard let contentKeyIdentifierString = keyRequest.identifier as? String, let contentKeyIdentifierURL = URL(string: contentKeyIdentifierString), let assetIDString = contentKeyIdentifierURL.host, let assetIDData = assetIDString.data(using: .utf8) else { print("Failed to retrieve the assetID from the keyRequest!") return } let provideOnlinekey: () -> Void = { () -> Void in do { let applicationCertificate = try self.requestApplicationCertificate() let completionHandler = { [weak self] (spcData: Data?, error: Error?) in guard let strongSelf = self else { return } if let error = error { keyRequest.processContentKeyResponseError(error) return } guard let spcData = spcData else { return } do { // Send SPC to Key Server and obtain CKC let ckcData = try strongSelf.requestContentKeyFromKeySecurityModule(spcData: spcData, assetID: assetIDString) /* AVContentKeyResponse is used to represent the data returned from the key server when requesting a key for decrypting content. */ let keyResponse = AVContentKeyResponse(fairPlayStreamingKeyResponseData: ckcData) /* Provide the content key response to make protected content available for processing. */ keyRequest.processContentKeyResponse(keyResponse) } catch { keyRequest.processContentKeyResponseError(error) } } keyRequest.makeStreamingContentKeyRequestData(forApp: applicationCertificate, contentIdentifier: assetIDData, options: [AVContentKeyRequestProtocolVersionsKey: [1]], completionHandler: completionHandler) } catch { keyRequest.processContentKeyResponseError(error) } } provideOnlinekey() } } should look more or less like this: function TContentKeyDelegate.requestApplicationCertificate(): NSData; var http: THTTPClient; Response: TMemoryStream; begin http := THTTPClient.Create; Response := TMemoryStream.Create; try http.ContentType := 'application/octet-stream'; http.Get(FAIRLPLAY_SERVER_URL, Response); result := TNSData.Wrap(TNSData.OCClass.dataWithBytes(response.Memory, response.Size)) finally Response.Free; http.Free; end; end; function TContentKeyDelegate.requestContentKeyFromKeySecurityModule(spcData: NSData; assetID: NSString): NSData; var http: THTTPClient; Response: TMemoryStream; Request: TMemoryStream; begin http := THTTPClient.Create; Response := TMemoryStream.Create; Request := TMemoryStream.Create; Request.Write(spcData.bytes^, spcData.length); try Request.Position := 0; http.ContentType := 'application/octet-stream'; http.Post(KEY_SERVER_URL, Request, Response); result := TNSData.Wrap(TNSData.OCClass.dataWithBytes(response.Memory, response.Size)) finally Request.Free; Response.Free; http.Free; end; end; procedure TContentKeyDelegate.requestCompleteHandler(contentKeyRequestData: NSData;error: NSError); var ckcData: NSData; keyResponse: AVContentKeyResponse; begin ckcData := requestContentKeyFromKeySecurityModule(contentKeyRequestData, FAssetIDString); keyResponse := TAVContentKeyResponse.Wrap(TAVContentKeyResponse.OCClass.contentKeyResponseWithFairPlayStreamingKeyResponseData(ckcData)); FKeyRequest.processContentKeyResponse(keyResponse); end; procedure TContentKeyDelegate.handleStreamingContentKeyRequest(keyRequest: AVContentKeyRequest); var contentKeyIdentifierString: NSString; assetIDData: NSData; contentKeyIdentifierURL: NSURL; dictionary: NSDictionary; begin FKeyRequest := keyRequest; contentKeyIdentifierString := TNSString.Wrap(keyRequest.identifier); contentKeyIdentifierURL := TNSUrl.Wrap(TNSUrl.OCClass.URLWithString(contentKeyIdentifierString)); FAssetIDString := contentKeyIdentifierURL.host; assetIDData := FAssetIDString.dataUsingEncoding(NSUTF8Stringencoding); dictionary := TNSDictionary.Wrap(TNSDictionary.OCClass.dictionaryWithObject(TNSNumber.OCClass.numberWithInt(1), NSObjectToID(AVContentKeyRequestProtocolVersionsKey))); keyRequest.makeStreamingContentKeyRequestDataForApp( requestApplicationCertificate, assetIDData, dictionary, requestCompleteHandler); end; I can post the whole unit if somebody is interested in. Using it with the FMX.Media class is quite simple. All you need is to assign the asset with the ContentKeyManager: LAsset := TAVURLAsset.Wrap(TAVURLAsset.OCClass.URLAssetWithURL(LURL, nil)); if LAsset.hasProtectedContent then ContentKeyManager.addContentKeyRecipient(LAsset); FPlayerItem := TAVPlayerItem.Wrap(TAVPlayerItem.OCClass.playerItemWithAsset(LAsset));
  15. CHackbart

    MacOS AVPlayer and DRM

    I do have one additional question, you might be also able to answer. ckcData is NSData and seem to be type casted to AVContentKeyResponse. I suppose something like keyResponse := TAVContentKeyResponse.Wrap(crcData) is not correct, right? And how do I fill a NSDictionary like options: [AVContentKeyRequestProtocolVersionsKey: [1]], And there is one function makeStreamingContentKeyRequestDataForApp which wants a NSData which is technically a string assetIDData = assetIDString.data(using: .utf8) Christian
×