Sign in Join
Wiki copytemporary

macOS Video Player: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: macOS Video Player

OldNewLine
1+*(macOS only)*
2+
3+## Overview
4+
5+AVPlayer is a controller object used to manage the playback and timing of a media asset. You can use an AVPlayer to play local and remote file-based media, such as QuickTime movies and MP3 audio files, as well as audiovisual media served using HTTPS Live Streaming. AVPlayer is for playing a single media asset at a time. You can reuse the player instance to play additional media assets using its *replaceCurrentItemWithPlayerItem:* method. The AVFoundation framework also provides a subclass of AVPlayer, called AVQueuePlayer, to create and manage the queuing of media assets to be played sequentially.
6+
7+An AVPlayer play media assets, which the AVFoundation framework models using the AVAsset class. AVAsset only models the static aspects of the media, such as its duration or creation date, and on its own, is unsuitable for playback with an AVPlayer. To play an asset, you need to create an instance of its dynamic counterpart found in AVPlayerItem. This object models the timing and presentation state of an asset played by an instance of AVPlayer.
8+
9+AVPlayer and AVPlayerItem are nonvisual objects which are unable to present an asset’s video onscreen. There are two main approaches you can take to present your video content onscreen:
10+
11+- AVKit: Apple recommends this as the best way to present your video content with the AVKit framework’s AVPlayerView class which presents the video content, along with playback controls and other media features giving you a full-featured playback experience.
12+
13+- AVPlayerLayer: If you want to build a custom interface for your player, use AVPlayerLayer. The player layer can be set as a view’s backing layer or can be added directly to the layer hierarchy. Unlike AVPlayerView, a player layer doesn’t present any playback controls, it just presents the visual content onscreen. You need to build the playback transport controls to play, pause, and seek through the media.
14+
15+## Supported file types
16+
17+AVPlayer supports media with the following Uniform Type Identifiers:
18+
19+| | |
20+|--------------------------------|---------------------|
21+| public.mpeg | public.mpeg-2-video |
22+| public.avi | public.aifc-audio |
23+| public.aac-audio | public.mpeg-4 |
24+| public.au-audio | public.aiff-audio |
25+| public.mp2 | public.3gpp2 |
26+| public.ac3-audio | public.mp3 |
27+| public.mpeg-2-transport-stream | public.3gpp |
28+| public.mpeg-4-audio | |
29+
30+AvPlayer supports media with the following file extensions:
31+
32+| | | | | | | | | | |
33+|-----|--------|------|-----|------|-----|------|-----|------|------|
34+| caf | ttml | au | ts | mqv | pls | flac | dv | amr | mp1 |
35+| mp3 | ac3 | loas | 3gp | aifc | m2v | m2t | m4b | m2a | m4r |
36+| aa | webvtt | aiff | m4a | scc | mp4 | m4p | mp2 | eac3 | mpa |
37+| vob | scc | aax | mpg | wav | mov | itt | xhe | m3u | m3u8 |
38+| mts | mod | vtt | m4v | 3g2 | sc2 | aac | mp4 | vtt | m1a |
39+| mp2 | avi | | | | | | | | |
40+
41+## AVPlayer vs AVAudioPlayer
42+
43+AVPlayer also plays audio only files as does [[macOS Audio Player|AVAudioPlayer]], so what are the advantages or disadvantages of using AVPlayer?
44+
45+- The documentation for AVPlayer states that the player works equally well with local and remote media files
46+- The documentation for AVAudioPlayer states that Apple recommends that you use this class for audio playback unless you are playing audio captured from a network stream.
47+- AVPlayer has more capabilities - see Apple's documentation (links below).
48+
49+## Application Transport Security
50+
51+Security. Yes, it sometimes gets in the way. If you try loading your remote content using an HTTP scheme, you will be prevented from doing so by Application Transport Security. To overcome this unwelcome security intervention, you will need to add this to your application's [[macOS property list files|.plist file]]:
52+
53+``` xml
54+ <key>NSAppTransportSecurity</key>
55+ <dict>
56+ <key>NSAllowsArbitraryLoadsForMedia</key>
57+ <true/>
58+ </dict>
59+```
60+
61+In the code examples below, loading the Apple URL *https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8* **may** require this addition to the .plist file:
62+
63+``` xml
64+ <key>NSAppTransportSecurity</key>
65+ <dict>
66+ <key>NSExceptionDomains</key>
67+ <dict>
68+ <key>devstreaming-cdn.apple.com</key>
69+ <dict>
70+ <key>NSExceptionRequiresForwardSecrecy</key>
71+ <false/>
72+ </dict>
73+ </dict>
74+ </dict>
75+```
76+
77+I say *may* because I started this code nearly two years ago I had included it, but with the effluxion of time no longer recall why it was needed. When testing today, the applications worked without it - \<shrug/\>.
78+
79+## Example code using AVPlayerView
80+
81+``` pascal
82+unit Unit1;
83+
84+{$mode objfpc}{$H+}
85+{$modeswitch objectivec1}
86+{$linkframework AvKit}
87+{$linkframework AVFoundation}
88+
89+// Enable/Disbale DEBUGGING
90+//{$define DEBUG}
91+
92+interface
93+
94+uses
95+ Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
96+ CTypes,
97+ MacOSAll,
98+ {$ifdef DEBUG}
99+ Cocoautils,
100+ {$endif}
101+ CocoaAll;
102+
103+type
104+
105+ { TForm1 }
106+
107+ TForm1 = class(TForm)
108+ LoadButton: TButton;
109+ QuitButton: TButton;
110+ procedure FormResize(Sender: TObject);
111+ procedure LoadButtonClick(Sender: TObject);
112+ procedure QuitButtonClick(Sender: TObject);
113+ private
114+
115+ public
116+
117+ end;
118+
119+ AVPlayerStatus = NSInteger;
120+ AVPlayerItemStatus = NSInteger;
121+ AVKeyValueStatus = NSInteger;
122+ AVAssetReferenceRestrictions = NSUInteger;
123+ AVPlayerViewControlsStyle = NSInteger;
124+
125+ CMTimeValue = cint64;
126+ CMTimeValuePtr = ^CMTimeValue;
127+ CMTimeScale = cint32;
128+ CMTimeScalePtr = ^CMTimeScale;
129+ CMTimeEpoch = cint64;
130+ CMTimeEpochPtr = ^CMTimeEpoch;
131+ CMTimeFlags = cuint32;
132+ CMTimeFlagsPtr = ^CMTimeFlags;
133+
134+ CMTime = record
135+ value: CMTimeValue;
136+ timescale: CMTimeScale;
137+ flags: CMTimeFlags;
138+ epoch: CMTimeEpoch;
139+ end;
140+
141+ AVAsynchronousKeyValueLoadingProtocol = objcprotocol external name 'AVAsynchronousKeyValueLoading'
142+ required
143+ function statusOfValueForKey_error (key: NSString; outError: NSErrorPtr): AVKeyValueStatus;
144+ message 'statusOfValueForKey:error:';
145+ procedure loadValuesAsynchronouslyForKeys_completionHandler (keys: NSArray; handler: OpaqueCBlock);
146+ message 'loadValuesAsynchronouslyForKeys:completionHandler:';
147+ end;
148+
149+ AVAsset = objcclass external (NSObject, NSCopyingProtocol, AVAsynchronousKeyValueLoadingProtocol)
150+ private
151+ _asset: id; //AVAssetInternal;
152+ public
153+ class function assetWithURL (URL: NSURL): id; message 'assetWithURL:';
154+ function duration: CMTime; message 'duration';
155+ function preferredRate: single; message 'preferredRate';
156+ function preferredVolume: single; message 'preferredVolume';
157+ function preferredTransform: CGAffineTransform; message 'preferredTransform';
158+ function naturalSize: CGSize; message 'naturalSize';
159+
160+ { Adopted protocols }
161+ function copyWithZone (mzone: NSZonePtr): id; message 'copyWithZone:';
162+ procedure loadValuesAsynchronouslyForKeys_completionHandler (keys: NSArray; handler: OpaqueCBlock);
163+ message 'loadValuesAsynchronouslyForKeys:completionHandler:';
164+ function statusOfValueForKey_error (mkey: NSString; outError: NSErrorPtr): AVKeyValueStatus;
165+ message 'statusOfValueForKey:error:';
166+ end;
167+
168+ AVPlayerItem = objcclass external (NSObject, NSCopyingProtocol)
169+ private
170+ _playerItem: id; //AVPlayerItemInternal;
171+ public
172+ class function playerItemWithURL (URL: NSURL): AVPlayerItem; message 'playerItemWithURL:';
173+ class function playerItemWithAsset (asset: AVAsset): AVPlayerItem; message 'playerItemWithAsset:';
174+ class function playerItemWithAsset_automaticallyLoadedAssetKeys (asset: AVAsset; automaticallyLoadedAssetKeys: NSArray): AVPlayerItem;
175+ message 'playerItemWithAsset:automaticallyLoadedAssetKeys:'; { available in 10_9, 7_0 }
176+ function initWithURL (URL: NSURL): id; message 'initWithURL:';
177+ function initWithAsset (asset: AVAsset): id; message 'initWithAsset:';
178+ function initWithAsset_automaticallyLoadedAssetKeys (asset: AVAsset; automaticallyLoadedAssetKeys: NSArray): id;
179+ message 'initWithAsset:automaticallyLoadedAssetKeys:'; { available in 10_9, 7_0 }
180+ function status: AVPlayerItemStatus; message 'status';
181+ function duration: CMTime; message 'duration';
182+ function error: NSError; message 'error';
183+
184+ { Adopted protocols }
185+ function copyWithZone (mzone: NSZonePtr): id; message 'copyWithZone:';
186+ end;
187+
188+ AVPlayer = objcclass external (NSObject)
189+ private
190+ _player: id; //AVPlayerInternal;
191+ public
192+ class function playerWithURL (URL: NSURL): id; message 'playerWithURL:';
193+ class function playerWithPlayerItem (item: AVPlayerItem): id; message 'playerWithPlayerItem:';
194+ function initWithURL (URL: NSURL): id; message 'initWithURL:';
195+ function initWithPlayerItem (item: AVPlayerItem): id; message 'initWithPlayerItem:';
196+ function currentItem: AVPlayerItem; message 'currentItem';
197+ function status: AVPlayerStatus; message 'status';
198+ function error: NSError; message 'error';
199+ procedure setVolume(newValue: Single); message 'setVolume:';
200+ procedure replaceCurrentItemWithPlayerItem(newItem: AVPlayerItem); message 'replaceCurrentItemWithPlayerItem:';
201+ end;
202+
203+ AVPlayerPlaybackControl = objccategory external (AVPlayer)
204+ procedure setRate(newValue: single); message 'setRate:';
205+ function rate: single; message 'rate';
206+ procedure play; message 'play';
207+ procedure pause; message 'pause';
208+ end;
209+
210+ AVPlayerView = objcclass external (NSView)
211+ public
212+ procedure setPlayer(newValue: AVPlayer); message 'setPlayer:';
213+ function player: AVPlayer; message 'player';
214+ procedure setControlsStyle(newValue: AVPlayerViewControlsStyle); message 'setControlsStyle:';
215+ function controlsStyle: AVPlayerViewControlsStyle; message 'controlsStyle';
216+ procedure setVideoGravity(newValue: NSString); message 'setVideoGravity:';
217+ function videoGravity: NSString; message 'videoGravity';
218+ function isReadyForDisplay: ObjCBOOL; message 'isReadyForDisplay';
219+ procedure setNeedsDisplay(newValue: ObjCBool); message 'setNeedsDisplay:';
220+ function videoBounds: NSRect; message 'videoBounds';
221+ function contentOverlayView: NSView; message 'contentOverlayView';
222+ end;
223+
224+const
225+ AVPlayerStatusUnknown = 0;
226+ AVPlayerStatusReadyToPlay = 1;
227+ AVPlayerStatusFailed = 2;
228+ AVPlayerItemStatusUnknown = 0;
229+ AVPlayerItemStatusReadyToPlay = 1;
230+ AVPlayerItemStatusFailed = 2;
231+ AVPlayerViewControlsStyleNone = 0;
232+ AVPlayerViewControlsStyleInline = 1;
233+ AVPlayerViewControlsStyleFloating = 2;
234+ AVPlayerViewControlsStyleMinimal = 3;
235+ AVPlayerViewControlsStyleDefault = AVPlayerViewControlsStyleInline;
236+ Resize = 2;
237+ ResizeAspect = 0;
238+ ResizeAspectFill = 1;
239+
240+var
241+ Form1: TForm1;
242+ myPlayer: AVPlayer = Nil;
243+ myAsset: AVAsset = Nil;
244+ myPlayerItem: AVPlayerItem = Nil;
245+ myPlayerView: AVPLayerView = Nil;
246+ myURL: NSUrl = Nil;
247+ myView: NSView = Nil;
248+ mins: Double = 0;
249+ secs: Double = 0;
250+ sixtysecs: Double = 60;
251+
252+implementation
253+
254+{$R *.lfm}
255+
256+{ TForm1 }
257+
258+procedure TForm1.LoadButtonClick(Sender: TObject);
259+begin
260+ // Prevent multiple instances of myPlayer
261+ if(myPlayer <> Nil) then
262+ begin
263+ myPlayer.pause;
264+ myPlayer.replaceCurrentItemWithPlayerItem(Nil);
265+ myPlayer := Nil;
266+ end;
267+
268+ //
269+ // 1. Create a URL
270+ //
271+ myURL := NSURL.URLWithString(NSSTR(PAnsiChar('https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8')));
272+ //myURL := NSURL.fileURLWithPath(NSStr(PAnsiChar('/Users/trev/alt.mp4')));
273+ if(myURL = Nil) then
274+ ShowMessage('myURL creation failed!');
275+
276+ //
277+ // 2. Create AVAsset, AVPlayerItem and AVPlayer objects
278+ //
279+ myAsset := AVAsset.alloc.assetWithURL(myURL);
280+ myPlayerItem := AVPlayerItem.alloc.initWithAsset(myAsset);
281+ myPlayer := AVPlayer.alloc.initWithPlayerItem(myPlayerItem);
282+
283+ // wait while media loads for playback
284+ while(myPlayerItem.Status = AVPlayerItemStatusUnknown)
285+ do
286+ application.processmessages;
287+
288+ {$ifdef DEBUG}
289+ // Status information for debugging
290+ if(myPlayer.Status = AVPlayerStatusFailed) then
291+ ShowMessage('AVplayer status: failed')
292+ else if(myPlayer.Status = AVPlayerStatusReadyToPlay) then
293+ ShowMessage('AVplayer status: ready to play');
294+ if(myPlayerItem.Status = AVPlayerItemStatusFailed) then
295+ ShowMessage('AVplayerItem status: failed' + '-' + NSStringToString(myPlayerItem.error.description))
296+ else if(myPlayerItem.Status = AVPlayerItemStatusReadyToPlay) then
297+ ShowMessage('AVplayerItem status: ready to play');
298+ {$endif}
299+
300+ //
301+ // 3. Create playerView object to display video
302+ //
303+ myPlayerView := AVPlayerView.alloc.initWithFrame(CGRectMake(0, 0, myView.frame.size.width, myView.frame.size.height));
304+ myPlayerView.setPlayer(myPlayer);
305+ myPlayerView.setControlsStyle(AVPlayerViewControlsStyleDefault);
306+
307+ // Enable resizing on the fly
308+ myPlayerView.setVideoGravity(NSStr('AVLayerVideoGravityResizeAspect'));
309+ myPlayerView.setAutoresizingMask(kCALayerHeightSizable or kCALayerWidthSizable);
310+
311+ // Make visible by adding PlayerView to Form1 as a subView
312+ myView := NSView(Form1.Handle);
313+ myView.addSubview(myPlayerView);
314+
315+ // Setup video frame size and bounds
316+ myPlayerView.setFrame(CGRectMake(0, 0, myView.frame.size.width, myView.frame.size.height -70));
317+ myPlayerView.setBounds(CGRectMake(0, 0, myView.frame.size.width, myView.frame.size.height -70));
318+
319+ {$ifdef DEBUG}
320+ // Status information for debugging
321+ mins := myPlayer.currentItem.duration.value / myPlayer.currentItem.duration.timescale / sixtysecs;
322+ secs := (myPlayer.currentItem.duration.value / myPlayer.currentItem.duration.timescale) - (Trunc(mins) * sixtysecs);
323+
324+ ShowMessage('Frame Height: ' + FloatToStr(myView.frame.size.height) + LineEnding
325+ + 'Frame Width : ' + FloatToStr(myView.frame.size.width) + LineEnding
326+ + 'Video gravity: ' + NSStringToString(myPlayerView.videoGravity) + LineEnding
327+ + 'Needs display: ' + BoolToStr(myPlayerView.needsDisplay) + LineEnding
328+ + 'Timescale: ' + FloatToStr(myPlayer.currentItem.duration.timescale) + LineEnding
329+ + 'Duration: ' + FloatToStr(myPlayer.currentItem.duration.value) + LineEnding
330+ + 'Flags: ' + FloatToStr(myPlayer.currentItem.duration.flags) + LineEnding
331+ + 'Epoch: ' + FloatToStr(myPlayer.currentItem.duration.epoch) + LineEnding
332+ + 'Minutes: ' + FloatToStr(myPlayer.currentItem.duration.value / myPlayer.currentItem.duration.timescale / sixtysecs) + LineEnding
333+ + 'MM:SS: ' + FloatToStr(Trunc(mins)) + ':' + FloatToStr(Trunc(Round(secs))) + LineEnding
334+ + 'Control style: ' + IntToStr(myPlayerView.controlsStyle));
335+
336+ if(myPlayerView.isReadyForDisplay) then
337+ ShowMessage('Ready for display')
338+ else
339+ ShowMessage('Not ready for display');
340+ {$endif}
341+end;
342+
343+//
344+// Resize PlayerView when Form resized
345+//
346+procedure TForm1.FormResize(Sender: TObject);
347+begin
348+ // Reset video frame size and bounds
349+ myPlayerView.setFrame(CGRectMake(0, 0, myView.frame.size.width, myView.frame.size.height -70));
350+ myPlayerView.setBounds(CGRectMake(0, 0, myView.frame.size.width, myView.frame.size.height -70));
351+end;
352+
353+//
354+// Quit player
355+//
356+procedure TForm1.QuitButtonClick(Sender: TObject);
357+begin
358+ Close;
359+end;
360+
361+finalization
362+
363+// release created objects
364+myPlayerView.release;
365+myPlayer.release;
366+myAsset.release;
367+
368+end.
369+```
370+
371+Full source code is available from [SourceForge](https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/laz_avplayer_view/).
372+
373+## Example code using AVPlayerLayer for a custom player
374+
375+``` pascal
376+unit Unit1;
377+
378+{$mode objfpc}{$H+}
379+{$modeswitch objectivec1}
380+{$linkframework AvKit}
381+{$linkframework AVFoundation}
382+
383+// Enable/Disbale DEBUGGING
384+//{$define DEBUG}
385+
386+interface
387+
388+uses
389+ Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
390+ CTypes,
391+ MacOSAll,
392+ {$ifdef DEBUG}
393+ CocoaUtils,
394+ {$endif}
395+ CocoaAll;
396+
397+type
398+
399+ { TForm1 }
400+
401+ TForm1 = class(TForm)
402+ ResumeButton: TButton;
403+ PauseButton: TButton;
404+ PlayButton: TButton;
405+ QuitButton: TButton;
406+ procedure PauseButtonClick(Sender: TObject);
407+ procedure PlayButtonClick(Sender: TObject);
408+ procedure QuitButtonClick(Sender: TObject);
409+ procedure ResumeButtonClick(Sender: TObject);
410+ private
411+
412+ public
413+
414+ end;
415+
416+ AVPlayerStatus = NSInteger;
417+ AVPlayerItemStatus = NSInteger;
418+ AVKeyValueStatus = NSInteger;
419+
420+ CMTimeValue = cint64;
421+ CMTimeValuePtr = ^CMTimeValue;
422+ CMTimeScale = cint32;
423+ CMTimeScalePtr = ^CMTimeScale;
424+ CMTimeEpoch = cint64;
425+ CMTimeEpochPtr = ^CMTimeEpoch;
426+ CMTimeFlags = cuint32;
427+ CMTimeFlagsPtr = ^CMTimeFlags;
428+
429+ CMTime = record
430+ value: CMTimeValue;
431+ timescale: CMTimeScale;
432+ flags: CMTimeFlags;
433+ epoch: CMTimeEpoch;
434+ end;
435+
436+ AVAsynchronousKeyValueLoadingProtocol = objcprotocol external name 'AVAsynchronousKeyValueLoading' required
437+ function statusOfValueForKey_error (key: NSString; outError: NSErrorPtr): AVKeyValueStatus;
438+ message 'statusOfValueForKey:error:';
439+ procedure loadValuesAsynchronouslyForKeys_completionHandler (keys: NSArray; handler: OpaqueCBlock);
440+ message 'loadValuesAsynchronouslyForKeys:completionHandler:';
441+ end;
442+
443+ AVAsset = objcclass external (NSObject, NSCopyingProtocol, AVAsynchronousKeyValueLoadingProtocol)
444+ private
445+ _asset: id; //AVAssetInternal;
446+ public
447+ class function assetWithURL (URL: NSURL): id; message 'assetWithURL:';
448+ function duration: CMTime; message 'duration';
449+ function preferredRate: single; message 'preferredRate';
450+ function preferredVolume: single; message 'preferredVolume';
451+ function preferredTransform: CGAffineTransform; message 'preferredTransform';
452+ function naturalSize: CGSize; message 'naturalSize';
453+
454+ { Adopted protocols }
455+ function copyWithZone (mzone: NSZonePtr): id; message 'copyWithZone:';
456+ procedure loadValuesAsynchronouslyForKeys_completionHandler (keys: NSArray; handler: OpaqueCBlock);
457+ message 'loadValuesAsynchronouslyForKeys:completionHandler:';
458+ function statusOfValueForKey_error (mkey: NSString; outError: NSErrorPtr): AVKeyValueStatus;
459+ message 'statusOfValueForKey:error:';
460+ end;
461+
462+ AVPlayerItem = objcclass external (NSObject, NSCopyingProtocol)
463+ private
464+ _playerItem: id; //AVPlayerItemInternal;
465+ public
466+ class function playerItemWithURL (URL: NSURL): AVPlayerItem; message 'playerItemWithURL:';
467+ class function playerItemWithAsset (asset: AVAsset): AVPlayerItem; message 'playerItemWithAsset:';
468+ class function playerItemWithAsset_automaticallyLoadedAssetKeys (asset: AVAsset; automaticallyLoadedAssetKeys: NSArray): AVPlayerItem;
469+ message 'playerItemWithAsset:automaticallyLoadedAssetKeys:'; { available in 10_9, 7_0 }
470+ function initWithURL (URL: NSURL): id; message 'initWithURL:';
471+ function initWithAsset (asset: AVAsset): id; message 'initWithAsset:';
472+ function initWithAsset_automaticallyLoadedAssetKeys (asset: AVAsset; automaticallyLoadedAssetKeys: NSArray): id;
473+ message 'initWithAsset:automaticallyLoadedAssetKeys:'; { available in 10_9, 7_0 }
474+ function status: AVPlayerItemStatus; message 'status';
475+ function error: NSError; message 'error';
476+
477+ { Adopted protocols }
478+ function copyWithZone (mzone: NSZonePtr): id; message 'copyWithZone:';
479+ end;
480+
481+ AVPlayer = objcclass external (NSObject)
482+ private
483+ _player: id; //AVPlayerInternal;
484+ public
485+ class function playerWithURL (URL: NSURL): id; message 'playerWithURL:';
486+ class function playerWithPlayerItem (item: AVPlayerItem): id; message 'playerWithPlayerItem:';
487+ function initWithURL (URL: NSURL): id; message 'initWithURL:';
488+ function initWithPlayerItem (item: AVPlayerItem): id; message 'initWithPlayerItem:';
489+ function status: AVPlayerStatus; message 'status';
490+ function error: NSError; message 'error';
491+ procedure setVolume(newValue: Single); message 'setVolume:';
492+ procedure replaceCurrentItemWithPlayerItem(newItem: AVPlayerItem); message 'replaceCurrentItemWithPlayerItem:';
493+ end;
494+
495+ AVURLAsset = objcclass external (AVAsset)
496+ private
497+ _URLAsset: id; //AVURLAssetInternal;
498+ public
499+ class function audiovisualTypes: NSArray; message 'audiovisualTypes'; { available in 10_7, 5_0 }
500+ class function audiovisualMIMETypes: NSArray; message 'audiovisualMIMETypes'; { available in 10_7, 5_0 }
501+ class function isPlayableExtendedMIMEType (extendedMIMEType: NSString): ObjCBOOL;
502+ message 'isPlayableExtendedMIMEType:'; { available in 10_7, 5_0 }
503+ class function URLAssetWithURL_options (URL: NSURL; options: NSDictionary): AVURLAsset;
504+ message 'URLAssetWithURL:options:';
505+ function initWithURL_options (URL: NSURL; options: NSDictionary): id;
506+ message 'initWithURL:options:';
507+ function URL: NSURL; message 'URL';
508+ end;
509+
510+ AVPlayerPlaybackControl = objccategory external (AVPlayer)
511+ procedure setRate(newValue: single); message 'setRate:';
512+ function rate: single; message 'rate';
513+ procedure play; message 'play';
514+ procedure pause; message 'pause';
515+ end;
516+
517+ AVPlayerLayer = objcclass external (CALayer)
518+ private
519+ _playerLayer: id; //AVPlayerLayerInternal;
520+ public
521+ class function playerLayerWithPlayer (player: AVPlayer): AVPlayerLayer;
522+ message 'playerLayerWithPlayer:';
523+ procedure setPlayer(newValue: AVPlayer); message 'setPlayer:';
524+ function player: AVPlayer; message 'player';
525+ procedure setVideoGravity(newValue: NSString); message 'setVideoGravity:';
526+ function videoGravity: NSString; message 'videoGravity';
527+ function isReadyForDisplay: ObjCBOOL; message 'isReadyForDisplay';
528+ function videoRect: CGRect; message 'videoRect';
529+ end;
530+
531+const
532+ AVPlayerStatusUnknown = 0;
533+ AVPlayerStatusReadyToPlay = 1;
534+ AVPlayerStatusFailed = 2;
535+
536+ AVPlayerItemStatusUnknown = 0;
537+ AVPlayerItemStatusReadyToPlay = 1;
538+ AVPlayerItemStatusFailed = 2;
539+
540+var
541+ Form1: TForm1;
542+ myPlayer: AVPlayer = Nil;
543+ myAsset: AVAsset = Nil;
544+ myPlayerItem: AVPlayerItem = Nil;
545+ myPlayerLayer: AVPlayerLayer = Nil;
546+ myURL: NSUrl = Nil;
547+ myView: NSView = Nil;
548+
549+implementation
550+
551+{$R *.lfm}
552+
553+{ TForm1 }
554+
555+//
556+// Play video or audio asset
557+//
558+procedure TForm1.PlayButtonClick(Sender: TObject);
559+begin
560+ // Prevent multiple instances of myPlayer
561+ if(myPlayer <> Nil) then
562+ begin
563+ myPlayer.pause;
564+ myPlayer.replaceCurrentItemWithPlayerItem(Nil);
565+ myPlayer := Nil;
566+ end;
567+
568+ //
569+ // 1. Create a URL
570+ //
571+ myURL := NSURL.URLWithString(NSSTR(PAnsiChar('https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8')));
572+ //myURL := NSURL.fileURLWithPath(NSStr(PAnsiChar('/Users/trev/alt.mp4')));
573+ if(myURL = Nil) then
574+ begin
575+ ShowMessage('myURL creation failed!');
576+ exit;
577+ end;
578+
579+ //
580+ // 2. Create AVAsset, AVPlayerItem and AVPlayer objects
581+ //
582+ myAsset := AVAsset.alloc.assetWithURL(myURL);
583+ myPlayerItem := AVPlayerItem.alloc.initWithAsset(myAsset);
584+ myPlayer := AVPlayer.alloc.initWithPlayerItem(myPlayerItem);
585+
586+ // Display media loading message
587+ loadingLabel.caption := 'Loading ...'; // or set in the Object Inspector
588+ loadingLabel.visible := True; // set to false in the Object Inspector
589+
590+ // Wait until status is known (while loading AVAsset media)
591+ while(myPlayer.Status = AVPlayerStatusUnknown)
592+ do
593+ application.processmessages;
594+
595+ loadingLabel.visible := False; // hide label now media loaded
596+
597+ {$ifdef DEBUG}
598+ // Status information for debugging
599+ if(myPlayer.Status = AVPlayerStatusFailed) then
600+ ShowMessage('AVplayer status: failed')
601+ else if(myPlayer.Status = AVPlayerStatusReadyToPlay) then
602+ ShowMessage('AVplayer status: ready to play');
603+ {$endif}
604+
605+ //
606+ // 3. Create PlayerLayer to display video
607+ //
608+ myPlayerLayer := AVPlayerLayer.playerLayerWithPlayer(myPlayer);
609+
610+ // enable resizing on the fly
611+ myPlayerLayer.setVideoGravity(NSStr('AVLayerVideoGravityResizeAspect'));
612+ myPlayerLayer.setAutoresizingMask(kCALayerHeightSizable or kCALayerWidthSizable);
613+
614+ // setup border color and border width
615+ myPlayerLayer.setBorderWidth(3);
616+ myPlayerLayer.setBorderColor(NSColor.yellowColor.cgColor);
617+
618+ // Make visible by adding PlayerLayer to Form1 View
619+ myView := NSView(Form1.Handle);
620+ myView.layer.addSublayer(myPlayerLayer);
621+
622+ // Setup video frame size and bounds
623+ myPlayerLayer.setFrame(CGRectMake(0, 0, myView.frame.size.width, myView.frame.size.height - 100));
624+ myPlayerLayer.setBounds(CGRectMake(0, 0, myView.frame.size.width, myView.frame.size.height - 100));
625+
626+ {$ifdef DEBUG}
627+ // Status Information for debugging
628+ ShowMessage('Frame Height: ' + FloatToStr(myView.frame.size.height) + LineEnding
629+ + 'Frame Width : ' + FloatToStr(myView.frame.size.width) + LineEnding
630+ + 'Video gravity: ' + NSStringToString(myPlayerLayer.videoGravity) + LineEnding
631+ + 'Needs display (layer): ' + BoolToStr(myPlayerLayer.needsDisplay) + LineEnding);
632+
633+ if(myPlayerLayer.isReadyForDisplay) then
634+ ShowMessage('PlayerLayer: Ready for display')
635+ else
636+ ShowMessage('PlayerLayer: Not ready for display');
637+ {$endif}
638+
639+ //
640+ // 4. Play
641+ //
642+ myPlayer.setRate(1); // play normal speed
643+ myPlayer.setVolume(0.5); // 50% volume
644+ //myPlayer.play; // NOT needed - setRate() starts playing at specified rate
645+end;
646+
647+//
648+// Pause playback
649+//
650+procedure TForm1.PauseButtonClick(Sender: TObject);
651+begin
652+ myPlayer.pause;
653+end;
654+
655+//
656+// Resume playback
657+//
658+procedure TForm1.ResumeButtonClick(Sender: TObject);
659+begin
660+ myPlayer.play;
661+end;
662+
663+//
664+// Quit player
665+//
666+procedure TForm1.QuitButtonClick(Sender: TObject);
667+begin
668+ Close;
669+end;
670+
671+finalisation
672+
673+// release created objects
674+myAsset.release;
675+myPlayerItem.release;
676+myPlayer.replaceCurrentItemWithPlayerItem(Nil);
677+myPlayer.release;
678+
679+end.
680+```
681+
682+Full source code available at [SourceForge](https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/laz_avplayer_layer/).
683+
684+## Detecting invalid AVAsset URLs
685+
686+If you feed AVAsset an invalid URL, you receive a very misleading error description from AVplayerItem.status.
687+
688+``` pascal
689+if(myPlayerItem.Status = AVPlayerItemStatusFailed) then
690+ ShowMessage('AVplayerItem status: failed: ' + NSStringToString(myPlayerItem.error.description));
691+```
692+
693+The above code produces the following error description:
694+
695+` AVplayerItem status: failed:{`\
696+` NSLocalizedDescription = "Operation Stopped";`\
697+` NSLocalizedFailureReason = "The server is not correctly configured.";`\
698+` NSUnderlyingError = "Error Domain=NSOSStatusErrorDomain Code=-12939 \"(null)\"";}`
699+
700+In reality, there is nothing wrong with the server at all; it's the invalid URL that causes the error. The only workaround I could find was to test the duration property value of AVAsset after myPlayerItem.Status leaves the AVPlayerItemStatusUnknown state with:
701+
702+``` pascal
703+// Check that URL is valid
704+if(myAsset.duration.value = 0) then
705+ begin
706+ ShowMessage('Error: Invalid URL - correct it and try again.');
707+ Exit;
708+ end;
709+```
710+
711+## Virtual machine issues with remote content
712+
713+The AVPlayer-based applications above do not work when retrieving remote content if you are running in a virtual machine. I have tried virtual machines in both Parallels Desktop v16 and VMware Fusion v12.2 and on two different Mac mini computers.
714+
715+Each attempt to retrieve the remote Apple file in the example code above simply results in a crossed out play symbol. The same behaviour is also exhibited by Safari, so it's not confined to these example applications. Local file content plays without issue.
716+
717+The error is:
718+
719+` Error Domain=AVFoundationErrorDomain Code=-11800`\
720+` "The operation could not be completed"`\
721+` UserInfo={NSLocalizedFailureReason=An unknown error occurred (-12151),`\
722+` NSLocalizedDescription=The operation could not be completed,`\
723+` NSUnderlyingError=0x101285070 {Error Domain=NSOSStatusErrorDomain Code=-12151"(null)"}}`
724+
725+## See also
726+
727+- [[macOS Audio Player]]
728+- [[macOS Audio Recorder]]
729+- [[macOS MIDI Player]]
730+- [[macOS NSSound]]
731+- [[macOS Sound Utilities]]
732+- [[macOS Play Alert Sound|System Sound Services]]
733+- [[Multimedia Programming]]
734+
735+## External links
736+
737+- [Apple: AVFoundation Framework](https://developer.apple.com/documentation/avfoundation)
738+- [Apple: Media Playback and Selection](https://developer.apple.com/documentation/avfoundation/media_playback_and_selection)
739+- [Apple: AVPlayer](https://developer.apple.com/documentation/avfoundation/avplayer)
740+- [Apple: AVAsset](https://developer.apple.com/documentation/avfoundation/avasset)
741+- [Apple: AVPlayerItem](https://developer.apple.com/documentation/avfoundation/avplayeritem)
742+- [Apple: AVPlayerView](https://developer.apple.com/documentation/avkit/avplayerview)
743+- [Apple: AVPlayerLayer](https://developer.apple.com/documentation/avfoundation/avplayerlayer)
744+
745+---
746+*From the Free Pascal wiki, page "macOS Video Player", as it stood on 2021-11-08 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
747+Category: Audio
748+Category: Code
749+Category: Multimedia
750+Category: Video
751+Category: macOS

Back to history