Sign in Join
Wiki copytemporary

macOS Video Player: what changed

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

OldNewLine
11*(macOS only)*
22
33## Overview
44
55AVPlayer 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.
66
77An 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.
88
99AVPlayer 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:
1010
1111- 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.
1212
1313- 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.
1414
1515## Supported file types
1616
1717AVPlayer supports media with the following Uniform Type Identifiers:
1818
1919| | |
2020|--------------------------------|---------------------|
2121| public.mpeg | public.mpeg-2-video |
2222| public.avi | public.aifc-audio |
2323| public.aac-audio | public.mpeg-4 |
2424| public.au-audio | public.aiff-audio |
2525| public.mp2 | public.3gpp2 |
2626| public.ac3-audio | public.mp3 |
2727| public.mpeg-2-transport-stream | public.3gpp |
2828| public.mpeg-4-audio | |
2929
3030AvPlayer supports media with the following file extensions:
3131
3232| | | | | | | | | | |
3333|-----|--------|------|-----|------|-----|------|-----|------|------|
3434| caf | ttml | au | ts | mqv | pls | flac | dv | amr | mp1 |
3535| mp3 | ac3 | loas | 3gp | aifc | m2v | m2t | m4b | m2a | m4r |
3636| aa | webvtt | aiff | m4a | scc | mp4 | m4p | mp2 | eac3 | mpa |
3737| vob | scc | aax | mpg | wav | mov | itt | xhe | m3u | m3u8 |
3838| mts | mod | vtt | m4v | 3g2 | sc2 | aac | mp4 | vtt | m1a |
3939| mp2 | avi | | | | | | | | |
4040
4141## AVPlayer vs AVAudioPlayer
4242
4343AVPlayer also plays audio only files as does [[macOS Audio Player|AVAudioPlayer]], so what are the advantages or disadvantages of using AVPlayer?
4444
4545- The documentation for AVPlayer states that the player works equally well with local and remote media files
4646- 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.
4747- AVPlayer has more capabilities - see Apple's documentation (links below).
4848
4949## Application Transport Security
5050
5151Security. 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]]:
5252
5353``` xml
5454 <key>NSAppTransportSecurity</key>
5555 <dict>
5656 <key>NSAllowsArbitraryLoadsForMedia</key>
5757 <true/>
5858 </dict>
5959```
6060
6161In 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:
6262
6363``` xml
6464 <key>NSAppTransportSecurity</key>
6565 <dict>
6666 <key>NSExceptionDomains</key>
6767 <dict>
6868 <key>devstreaming-cdn.apple.com</key>
6969 <dict>
7070 <key>NSExceptionRequiresForwardSecrecy</key>
7171 <false/>
7272 </dict>
7373 </dict>
7474 </dict>
7575```
7676
7777I 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/\>.
7878
7979## Example code using AVPlayerView
8080
8181``` pascal
8282unit Unit1;
8383
8484{$mode objfpc}{$H+}
8585{$modeswitch objectivec1}
8686{$linkframework AvKit}
8787{$linkframework AVFoundation}
8888
8989// Enable/Disbale DEBUGGING
9090//{$define DEBUG}
9191
9292interface
9393
9494uses
9595 Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
9696 CTypes,
9797 MacOSAll,
9898 {$ifdef DEBUG}
9999 Cocoautils,
100100 {$endif}
101101 CocoaAll;
102102
103103type
104104
105105 { TForm1 }
106106
107107 TForm1 = class(TForm)
108108 LoadButton: TButton;
109109 QuitButton: TButton;
110110 procedure FormResize(Sender: TObject);
111111 procedure LoadButtonClick(Sender: TObject);
112112 procedure QuitButtonClick(Sender: TObject);
113113 private
114114
115115 public
116116
117117 end;
118118
119119 AVPlayerStatus = NSInteger;
120120 AVPlayerItemStatus = NSInteger;
121121 AVKeyValueStatus = NSInteger;
122122 AVAssetReferenceRestrictions = NSUInteger;
123123 AVPlayerViewControlsStyle = NSInteger;
124124
125125 CMTimeValue = cint64;
126126 CMTimeValuePtr = ^CMTimeValue;
127127 CMTimeScale = cint32;
128128 CMTimeScalePtr = ^CMTimeScale;
129129 CMTimeEpoch = cint64;
130130 CMTimeEpochPtr = ^CMTimeEpoch;
131131 CMTimeFlags = cuint32;
132132 CMTimeFlagsPtr = ^CMTimeFlags;
133133
134134 CMTime = record
135135 value: CMTimeValue;
136136 timescale: CMTimeScale;
137137 flags: CMTimeFlags;
138138 epoch: CMTimeEpoch;
139139 end;
140140
141141 AVAsynchronousKeyValueLoadingProtocol = objcprotocol external name 'AVAsynchronousKeyValueLoading'
142142 required
143143 function statusOfValueForKey_error (key: NSString; outError: NSErrorPtr): AVKeyValueStatus;
144144 message 'statusOfValueForKey:error:';
145145 procedure loadValuesAsynchronouslyForKeys_completionHandler (keys: NSArray; handler: OpaqueCBlock);
146146 message 'loadValuesAsynchronouslyForKeys:completionHandler:';
147147 end;
148148
149149 AVAsset = objcclass external (NSObject, NSCopyingProtocol, AVAsynchronousKeyValueLoadingProtocol)
150150 private
151151 _asset: id; //AVAssetInternal;
152152 public
153153 class function assetWithURL (URL: NSURL): id; message 'assetWithURL:';
154154 function duration: CMTime; message 'duration';
155155 function preferredRate: single; message 'preferredRate';
156156 function preferredVolume: single; message 'preferredVolume';
157157 function preferredTransform: CGAffineTransform; message 'preferredTransform';
158158 function naturalSize: CGSize; message 'naturalSize';
159159
160160 { Adopted protocols }
161161 function copyWithZone (mzone: NSZonePtr): id; message 'copyWithZone:';
162162 procedure loadValuesAsynchronouslyForKeys_completionHandler (keys: NSArray; handler: OpaqueCBlock);
163163 message 'loadValuesAsynchronouslyForKeys:completionHandler:';
164164 function statusOfValueForKey_error (mkey: NSString; outError: NSErrorPtr): AVKeyValueStatus;
165165 message 'statusOfValueForKey:error:';
166166 end;
167167
168168 AVPlayerItem = objcclass external (NSObject, NSCopyingProtocol)
169169 private
170170 _playerItem: id; //AVPlayerItemInternal;
171171 public
172172 class function playerItemWithURL (URL: NSURL): AVPlayerItem; message 'playerItemWithURL:';
173173 class function playerItemWithAsset (asset: AVAsset): AVPlayerItem; message 'playerItemWithAsset:';
174174 class function playerItemWithAsset_automaticallyLoadedAssetKeys (asset: AVAsset; automaticallyLoadedAssetKeys: NSArray): AVPlayerItem;
175175 message 'playerItemWithAsset:automaticallyLoadedAssetKeys:'; { available in 10_9, 7_0 }
176176 function initWithURL (URL: NSURL): id; message 'initWithURL:';
177177 function initWithAsset (asset: AVAsset): id; message 'initWithAsset:';
178178 function initWithAsset_automaticallyLoadedAssetKeys (asset: AVAsset; automaticallyLoadedAssetKeys: NSArray): id;
179179 message 'initWithAsset:automaticallyLoadedAssetKeys:'; { available in 10_9, 7_0 }
180180 function status: AVPlayerItemStatus; message 'status';
181181 function duration: CMTime; message 'duration';
182182 function error: NSError; message 'error';
183183
184184 { Adopted protocols }
185185 function copyWithZone (mzone: NSZonePtr): id; message 'copyWithZone:';
186186 end;
187187
188188 AVPlayer = objcclass external (NSObject)
189189 private
190190 _player: id; //AVPlayerInternal;
191191 public
192192 class function playerWithURL (URL: NSURL): id; message 'playerWithURL:';
193193 class function playerWithPlayerItem (item: AVPlayerItem): id; message 'playerWithPlayerItem:';
194194 function initWithURL (URL: NSURL): id; message 'initWithURL:';
195195 function initWithPlayerItem (item: AVPlayerItem): id; message 'initWithPlayerItem:';
196196 function currentItem: AVPlayerItem; message 'currentItem';
197197 function status: AVPlayerStatus; message 'status';
198198 function error: NSError; message 'error';
199199 procedure setVolume(newValue: Single); message 'setVolume:';
200200 procedure replaceCurrentItemWithPlayerItem(newItem: AVPlayerItem); message 'replaceCurrentItemWithPlayerItem:';
201201 end;
202202
203203 AVPlayerPlaybackControl = objccategory external (AVPlayer)
204204 procedure setRate(newValue: single); message 'setRate:';
205205 function rate: single; message 'rate';
206206 procedure play; message 'play';
207207 procedure pause; message 'pause';
208208 end;
209209
210210 AVPlayerView = objcclass external (NSView)
211211 public
212212 procedure setPlayer(newValue: AVPlayer); message 'setPlayer:';
213213 function player: AVPlayer; message 'player';
214214 procedure setControlsStyle(newValue: AVPlayerViewControlsStyle); message 'setControlsStyle:';
215215 function controlsStyle: AVPlayerViewControlsStyle; message 'controlsStyle';
216216 procedure setVideoGravity(newValue: NSString); message 'setVideoGravity:';
217217 function videoGravity: NSString; message 'videoGravity';
218218 function isReadyForDisplay: ObjCBOOL; message 'isReadyForDisplay';
219219 procedure setNeedsDisplay(newValue: ObjCBool); message 'setNeedsDisplay:';
220220 function videoBounds: NSRect; message 'videoBounds';
221221 function contentOverlayView: NSView; message 'contentOverlayView';
222222 end;
223223
224224const
225225 AVPlayerStatusUnknown = 0;
226226 AVPlayerStatusReadyToPlay = 1;
227227 AVPlayerStatusFailed = 2;
228228 AVPlayerItemStatusUnknown = 0;
229229 AVPlayerItemStatusReadyToPlay = 1;
230230 AVPlayerItemStatusFailed = 2;
231231 AVPlayerViewControlsStyleNone = 0;
232232 AVPlayerViewControlsStyleInline = 1;
233233 AVPlayerViewControlsStyleFloating = 2;
234234 AVPlayerViewControlsStyleMinimal = 3;
235235 AVPlayerViewControlsStyleDefault = AVPlayerViewControlsStyleInline;
236236 Resize = 2;
237237 ResizeAspect = 0;
238238 ResizeAspectFill = 1;
239239
240240var
241241 Form1: TForm1;
242242 myPlayer: AVPlayer = Nil;
243243 myAsset: AVAsset = Nil;
244244 myPlayerItem: AVPlayerItem = Nil;
245245 myPlayerView: AVPLayerView = Nil;
246246 myURL: NSUrl = Nil;
247247 myView: NSView = Nil;
248248 mins: Double = 0;
249249 secs: Double = 0;
250250 sixtysecs: Double = 60;
251251
252252implementation
253253
254254{$R *.lfm}
255255
256256{ TForm1 }
257257
258258procedure TForm1.LoadButtonClick(Sender: TObject);
259259begin
260260 // Prevent multiple instances of myPlayer
261261 if(myPlayer <> Nil) then
262262 begin
263263 myPlayer.pause;
264264 myPlayer.replaceCurrentItemWithPlayerItem(Nil);
265265 myPlayer := Nil;
266266 end;
267267
268268 //
269269 // 1. Create a URL
270270 //
271271 myURL := NSURL.URLWithString(NSSTR(PAnsiChar('https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8')));
272272 //myURL := NSURL.fileURLWithPath(NSStr(PAnsiChar('/Users/trev/alt.mp4')));
273273 if(myURL = Nil) then
274274 ShowMessage('myURL creation failed!');
275275
276276 //
277277 // 2. Create AVAsset, AVPlayerItem and AVPlayer objects
278278 //
279279 myAsset := AVAsset.alloc.assetWithURL(myURL);
280280 myPlayerItem := AVPlayerItem.alloc.initWithAsset(myAsset);
281281 myPlayer := AVPlayer.alloc.initWithPlayerItem(myPlayerItem);
282282
283283 // wait while media loads for playback
284284 while(myPlayerItem.Status = AVPlayerItemStatusUnknown)
285285 do
286286 application.processmessages;
287287
288288 {$ifdef DEBUG}
289289 // Status information for debugging
290290 if(myPlayer.Status = AVPlayerStatusFailed) then
291291 ShowMessage('AVplayer status: failed')
292292 else if(myPlayer.Status = AVPlayerStatusReadyToPlay) then
293293 ShowMessage('AVplayer status: ready to play');
294294 if(myPlayerItem.Status = AVPlayerItemStatusFailed) then
295295 ShowMessage('AVplayerItem status: failed' + '-' + NSStringToString(myPlayerItem.error.description))
296296 else if(myPlayerItem.Status = AVPlayerItemStatusReadyToPlay) then
297297 ShowMessage('AVplayerItem status: ready to play');
298298 {$endif}
299299
300300 //
301301 // 3. Create playerView object to display video
302302 //
303303 myPlayerView := AVPlayerView.alloc.initWithFrame(CGRectMake(0, 0, myView.frame.size.width, myView.frame.size.height));
304304 myPlayerView.setPlayer(myPlayer);
305305 myPlayerView.setControlsStyle(AVPlayerViewControlsStyleDefault);
306306
307307 // Enable resizing on the fly
308308 myPlayerView.setVideoGravity(NSStr('AVLayerVideoGravityResizeAspect'));
309309 myPlayerView.setAutoresizingMask(kCALayerHeightSizable or kCALayerWidthSizable);
310310
311311 // Make visible by adding PlayerView to Form1 as a subView
312312 myView := NSView(Form1.Handle);
313313 myView.addSubview(myPlayerView);
314314
315315 // Setup video frame size and bounds
316316 myPlayerView.setFrame(CGRectMake(0, 0, myView.frame.size.width, myView.frame.size.height -70));
317317 myPlayerView.setBounds(CGRectMake(0, 0, myView.frame.size.width, myView.frame.size.height -70));
318318
319319 {$ifdef DEBUG}
320320 // Status information for debugging
321321 mins := myPlayer.currentItem.duration.value / myPlayer.currentItem.duration.timescale / sixtysecs;
322322 secs := (myPlayer.currentItem.duration.value / myPlayer.currentItem.duration.timescale) - (Trunc(mins) * sixtysecs);
323323
324324 ShowMessage('Frame Height: ' + FloatToStr(myView.frame.size.height) + LineEnding
325325 + 'Frame Width : ' + FloatToStr(myView.frame.size.width) + LineEnding
326326 + 'Video gravity: ' + NSStringToString(myPlayerView.videoGravity) + LineEnding
327327 + 'Needs display: ' + BoolToStr(myPlayerView.needsDisplay) + LineEnding
328328 + 'Timescale: ' + FloatToStr(myPlayer.currentItem.duration.timescale) + LineEnding
329329 + 'Duration: ' + FloatToStr(myPlayer.currentItem.duration.value) + LineEnding
330330 + 'Flags: ' + FloatToStr(myPlayer.currentItem.duration.flags) + LineEnding
331331 + 'Epoch: ' + FloatToStr(myPlayer.currentItem.duration.epoch) + LineEnding
332332 + 'Minutes: ' + FloatToStr(myPlayer.currentItem.duration.value / myPlayer.currentItem.duration.timescale / sixtysecs) + LineEnding
333333 + 'MM:SS: ' + FloatToStr(Trunc(mins)) + ':' + FloatToStr(Trunc(Round(secs))) + LineEnding
334334 + 'Control style: ' + IntToStr(myPlayerView.controlsStyle));
335335
336336 if(myPlayerView.isReadyForDisplay) then
337337 ShowMessage('Ready for display')
338338 else
339339 ShowMessage('Not ready for display');
340340 {$endif}
341341end;
342342
343343//
344344// Resize PlayerView when Form resized
345345//
346346procedure TForm1.FormResize(Sender: TObject);
347347begin
348348 // Reset video frame size and bounds
349349 myPlayerView.setFrame(CGRectMake(0, 0, myView.frame.size.width, myView.frame.size.height -70));
350350 myPlayerView.setBounds(CGRectMake(0, 0, myView.frame.size.width, myView.frame.size.height -70));
351351end;
352352
353353//
354354// Quit player
355355//
356356procedure TForm1.QuitButtonClick(Sender: TObject);
357357begin
358358 Close;
359359end;
360360
361361finalization
362362
363363// release created objects
364364myPlayerView.release;
365365myPlayer.release;
366366myAsset.release;
367367
368368end.
369369```
370370
371371Full source code is available from [SourceForge](https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/laz_avplayer_view/).
372372
373373## Example code using AVPlayerLayer for a custom player
374374
375375``` pascal
376376unit Unit1;
377377
378378{$mode objfpc}{$H+}
379379{$modeswitch objectivec1}
380380{$linkframework AvKit}
381381{$linkframework AVFoundation}
382382
383383// Enable/Disbale DEBUGGING
384384//{$define DEBUG}
385385
386386interface
387387
388388uses
389389 Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
390390 CTypes,
391391 MacOSAll,
392392 {$ifdef DEBUG}
393393 CocoaUtils,
394394 {$endif}
395395 CocoaAll;
396396
397397type
398398
399399 { TForm1 }
400400
401401 TForm1 = class(TForm)
402402 ResumeButton: TButton;
403403 PauseButton: TButton;
404404 PlayButton: TButton;
405405 QuitButton: TButton;
406406 procedure PauseButtonClick(Sender: TObject);
407407 procedure PlayButtonClick(Sender: TObject);
408408 procedure QuitButtonClick(Sender: TObject);
409409 procedure ResumeButtonClick(Sender: TObject);
410410 private
411411
412412 public
413413
414414 end;
415415
416416 AVPlayerStatus = NSInteger;
417417 AVPlayerItemStatus = NSInteger;
418418 AVKeyValueStatus = NSInteger;
419419
420420 CMTimeValue = cint64;
421421 CMTimeValuePtr = ^CMTimeValue;
422422 CMTimeScale = cint32;
423423 CMTimeScalePtr = ^CMTimeScale;
424424 CMTimeEpoch = cint64;
425425 CMTimeEpochPtr = ^CMTimeEpoch;
426426 CMTimeFlags = cuint32;
427427 CMTimeFlagsPtr = ^CMTimeFlags;
428428
429429 CMTime = record
430430 value: CMTimeValue;
431431 timescale: CMTimeScale;
432432 flags: CMTimeFlags;
433433 epoch: CMTimeEpoch;
434434 end;
435435
436436 AVAsynchronousKeyValueLoadingProtocol = objcprotocol external name 'AVAsynchronousKeyValueLoading' required
437437 function statusOfValueForKey_error (key: NSString; outError: NSErrorPtr): AVKeyValueStatus;
438438 message 'statusOfValueForKey:error:';
439439 procedure loadValuesAsynchronouslyForKeys_completionHandler (keys: NSArray; handler: OpaqueCBlock);
440440 message 'loadValuesAsynchronouslyForKeys:completionHandler:';
441441 end;
442442
443443 AVAsset = objcclass external (NSObject, NSCopyingProtocol, AVAsynchronousKeyValueLoadingProtocol)
444444 private
445445 _asset: id; //AVAssetInternal;
446446 public
447447 class function assetWithURL (URL: NSURL): id; message 'assetWithURL:';
448448 function duration: CMTime; message 'duration';
449449 function preferredRate: single; message 'preferredRate';
450450 function preferredVolume: single; message 'preferredVolume';
451451 function preferredTransform: CGAffineTransform; message 'preferredTransform';
452452 function naturalSize: CGSize; message 'naturalSize';
453453
454454 { Adopted protocols }
455455 function copyWithZone (mzone: NSZonePtr): id; message 'copyWithZone:';
456456 procedure loadValuesAsynchronouslyForKeys_completionHandler (keys: NSArray; handler: OpaqueCBlock);
457457 message 'loadValuesAsynchronouslyForKeys:completionHandler:';
458458 function statusOfValueForKey_error (mkey: NSString; outError: NSErrorPtr): AVKeyValueStatus;
459459 message 'statusOfValueForKey:error:';
460460 end;
461461
462462 AVPlayerItem = objcclass external (NSObject, NSCopyingProtocol)
463463 private
464464 _playerItem: id; //AVPlayerItemInternal;
465465 public
466466 class function playerItemWithURL (URL: NSURL): AVPlayerItem; message 'playerItemWithURL:';
467467 class function playerItemWithAsset (asset: AVAsset): AVPlayerItem; message 'playerItemWithAsset:';
468468 class function playerItemWithAsset_automaticallyLoadedAssetKeys (asset: AVAsset; automaticallyLoadedAssetKeys: NSArray): AVPlayerItem;
469469 message 'playerItemWithAsset:automaticallyLoadedAssetKeys:'; { available in 10_9, 7_0 }
470470 function initWithURL (URL: NSURL): id; message 'initWithURL:';
471471 function initWithAsset (asset: AVAsset): id; message 'initWithAsset:';
472472 function initWithAsset_automaticallyLoadedAssetKeys (asset: AVAsset; automaticallyLoadedAssetKeys: NSArray): id;
473473 message 'initWithAsset:automaticallyLoadedAssetKeys:'; { available in 10_9, 7_0 }
474474 function status: AVPlayerItemStatus; message 'status';
475475 function error: NSError; message 'error';
476476
477477 { Adopted protocols }
478478 function copyWithZone (mzone: NSZonePtr): id; message 'copyWithZone:';
479479 end;
480480
481481 AVPlayer = objcclass external (NSObject)
482482 private
483483 _player: id; //AVPlayerInternal;
484484 public
485485 class function playerWithURL (URL: NSURL): id; message 'playerWithURL:';
486486 class function playerWithPlayerItem (item: AVPlayerItem): id; message 'playerWithPlayerItem:';
487487 function initWithURL (URL: NSURL): id; message 'initWithURL:';
488488 function initWithPlayerItem (item: AVPlayerItem): id; message 'initWithPlayerItem:';
489489 function status: AVPlayerStatus; message 'status';
490490 function error: NSError; message 'error';
491491 procedure setVolume(newValue: Single); message 'setVolume:';
492492 procedure replaceCurrentItemWithPlayerItem(newItem: AVPlayerItem); message 'replaceCurrentItemWithPlayerItem:';
493493 end;
494494
495495 AVURLAsset = objcclass external (AVAsset)
496496 private
497497 _URLAsset: id; //AVURLAssetInternal;
498498 public
499499 class function audiovisualTypes: NSArray; message 'audiovisualTypes'; { available in 10_7, 5_0 }
500500 class function audiovisualMIMETypes: NSArray; message 'audiovisualMIMETypes'; { available in 10_7, 5_0 }
501501 class function isPlayableExtendedMIMEType (extendedMIMEType: NSString): ObjCBOOL;
502502 message 'isPlayableExtendedMIMEType:'; { available in 10_7, 5_0 }
503503 class function URLAssetWithURL_options (URL: NSURL; options: NSDictionary): AVURLAsset;
504504 message 'URLAssetWithURL:options:';
505505 function initWithURL_options (URL: NSURL; options: NSDictionary): id;
506506 message 'initWithURL:options:';
507507 function URL: NSURL; message 'URL';
508508 end;
509509
510510 AVPlayerPlaybackControl = objccategory external (AVPlayer)
511511 procedure setRate(newValue: single); message 'setRate:';
512512 function rate: single; message 'rate';
513513 procedure play; message 'play';
514514 procedure pause; message 'pause';
515515 end;
516516
517517 AVPlayerLayer = objcclass external (CALayer)
518518 private
519519 _playerLayer: id; //AVPlayerLayerInternal;
520520 public
521521 class function playerLayerWithPlayer (player: AVPlayer): AVPlayerLayer;
522522 message 'playerLayerWithPlayer:';
523523 procedure setPlayer(newValue: AVPlayer); message 'setPlayer:';
524524 function player: AVPlayer; message 'player';
525525 procedure setVideoGravity(newValue: NSString); message 'setVideoGravity:';
526526 function videoGravity: NSString; message 'videoGravity';
527527 function isReadyForDisplay: ObjCBOOL; message 'isReadyForDisplay';
528528 function videoRect: CGRect; message 'videoRect';
529529 end;
530530
531531const
532532 AVPlayerStatusUnknown = 0;
533533 AVPlayerStatusReadyToPlay = 1;
534534 AVPlayerStatusFailed = 2;
535535
536536 AVPlayerItemStatusUnknown = 0;
537537 AVPlayerItemStatusReadyToPlay = 1;
538538 AVPlayerItemStatusFailed = 2;
539539
540540var
541541 Form1: TForm1;
542542 myPlayer: AVPlayer = Nil;
543543 myAsset: AVAsset = Nil;
544544 myPlayerItem: AVPlayerItem = Nil;
545545 myPlayerLayer: AVPlayerLayer = Nil;
546546 myURL: NSUrl = Nil;
547547 myView: NSView = Nil;
548548
549549implementation
550550
551551{$R *.lfm}
552552
553553{ TForm1 }
554554
555555//
556556// Play video or audio asset
557557//
558558procedure TForm1.PlayButtonClick(Sender: TObject);
559559begin
560560 // Prevent multiple instances of myPlayer
561561 if(myPlayer <> Nil) then
562562 begin
563563 myPlayer.pause;
564564 myPlayer.replaceCurrentItemWithPlayerItem(Nil);
565565 myPlayer := Nil;
566566 end;
567567
568568 //
569569 // 1. Create a URL
570570 //
571571 myURL := NSURL.URLWithString(NSSTR(PAnsiChar('https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8')));
572572 //myURL := NSURL.fileURLWithPath(NSStr(PAnsiChar('/Users/trev/alt.mp4')));
573573 if(myURL = Nil) then
574574 begin
575575 ShowMessage('myURL creation failed!');
576576 exit;
577577 end;
578578
579579 //
580580 // 2. Create AVAsset, AVPlayerItem and AVPlayer objects
581581 //
582582 myAsset := AVAsset.alloc.assetWithURL(myURL);
583583 myPlayerItem := AVPlayerItem.alloc.initWithAsset(myAsset);
584584 myPlayer := AVPlayer.alloc.initWithPlayerItem(myPlayerItem);
585585
586586 // Display media loading message
587587 loadingLabel.caption := 'Loading ...'; // or set in the Object Inspector
588588 loadingLabel.visible := True; // set to false in the Object Inspector
589589
590590 // Wait until status is known (while loading AVAsset media)
591591 while(myPlayer.Status = AVPlayerStatusUnknown)
592592 do
593593 application.processmessages;
594594
595595 loadingLabel.visible := False; // hide label now media loaded
596596
597597 {$ifdef DEBUG}
598598 // Status information for debugging
599599 if(myPlayer.Status = AVPlayerStatusFailed) then
600600 ShowMessage('AVplayer status: failed')
601601 else if(myPlayer.Status = AVPlayerStatusReadyToPlay) then
602602 ShowMessage('AVplayer status: ready to play');
603603 {$endif}
604604
605605 //
606606 // 3. Create PlayerLayer to display video
607607 //
608608 myPlayerLayer := AVPlayerLayer.playerLayerWithPlayer(myPlayer);
609609
610610 // enable resizing on the fly
611611 myPlayerLayer.setVideoGravity(NSStr('AVLayerVideoGravityResizeAspect'));
612612 myPlayerLayer.setAutoresizingMask(kCALayerHeightSizable or kCALayerWidthSizable);
613613
614614 // setup border color and border width
615615 myPlayerLayer.setBorderWidth(3);
616616 myPlayerLayer.setBorderColor(NSColor.yellowColor.cgColor);
617617
618618 // Make visible by adding PlayerLayer to Form1 View
619619 myView := NSView(Form1.Handle);
620620 myView.layer.addSublayer(myPlayerLayer);
621621
622622 // Setup video frame size and bounds
623623 myPlayerLayer.setFrame(CGRectMake(0, 0, myView.frame.size.width, myView.frame.size.height - 100));
624624 myPlayerLayer.setBounds(CGRectMake(0, 0, myView.frame.size.width, myView.frame.size.height - 100));
625625
626626 {$ifdef DEBUG}
627627 // Status Information for debugging
628628 ShowMessage('Frame Height: ' + FloatToStr(myView.frame.size.height) + LineEnding
629629 + 'Frame Width : ' + FloatToStr(myView.frame.size.width) + LineEnding
630630 + 'Video gravity: ' + NSStringToString(myPlayerLayer.videoGravity) + LineEnding
631631 + 'Needs display (layer): ' + BoolToStr(myPlayerLayer.needsDisplay) + LineEnding);
632632
633633 if(myPlayerLayer.isReadyForDisplay) then
634634 ShowMessage('PlayerLayer: Ready for display')
635635 else
636636 ShowMessage('PlayerLayer: Not ready for display');
637637 {$endif}
638638
639639 //
640640 // 4. Play
641641 //
642642 myPlayer.setRate(1); // play normal speed
643643 myPlayer.setVolume(0.5); // 50% volume
644644 //myPlayer.play; // NOT needed - setRate() starts playing at specified rate
645645end;
646646
647647//
648648// Pause playback
649649//
650650procedure TForm1.PauseButtonClick(Sender: TObject);
651651begin
652652 myPlayer.pause;
653653end;
654654
655655//
656656// Resume playback
657657//
658658procedure TForm1.ResumeButtonClick(Sender: TObject);
659659begin
660660 myPlayer.play;
661661end;
662662
663663//
664664// Quit player
665665//
666666procedure TForm1.QuitButtonClick(Sender: TObject);
667667begin
668668 Close;
669669end;
670670
671671finalisation
672672
673673// release created objects
674674myAsset.release;
675675myPlayerItem.release;
676676myPlayer.replaceCurrentItemWithPlayerItem(Nil);
677677myPlayer.release;
678678
679679end.
680680```
681681
682682Full source code available at [SourceForge](https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/laz_avplayer_layer/).
683683
684684## Detecting invalid AVAsset URLs
685685
686686If you feed AVAsset an invalid URL, you receive a very misleading error description from AVplayerItem.status.
687687
688688``` pascal
689689if(myPlayerItem.Status = AVPlayerItemStatusFailed) then
690690 ShowMessage('AVplayerItem status: failed: ' + NSStringToString(myPlayerItem.error.description));
691691```
692692
693693The above code produces the following error description:
694694
695-` AVplayerItem status: failed:{`\
696-` NSLocalizedDescription = "Operation Stopped";`\
697-` NSLocalizedFailureReason = "The server is not correctly configured.";`\
698-` NSUnderlyingError = "Error Domain=NSOSStatusErrorDomain Code=-12939 \"(null)\"";}`
695+```text
696+ AVplayerItem status: failed:{
697+ NSLocalizedDescription = "Operation Stopped";
698+ NSLocalizedFailureReason = "The server is not correctly configured.";
699+ NSUnderlyingError = "Error Domain=NSOSStatusErrorDomain Code=-12939 \"(null)\"";}
700+```
699701
700702In 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:
701703
702704``` pascal
703705// Check that URL is valid
704706if(myAsset.duration.value = 0) then
705707 begin
706708 ShowMessage('Error: Invalid URL - correct it and try again.');
707709 Exit;
708710 end;
709711```
710712
711713## Virtual machine issues with remote content
712714
713715The 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.
714716
715717Each 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.
716718
717719The error is:
718720
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)"}}`
721+```text
722+ Error Domain=AVFoundationErrorDomain Code=-11800
723+ "The operation could not be completed"
724+ UserInfo={NSLocalizedFailureReason=An unknown error occurred (-12151),
725+ NSLocalizedDescription=The operation could not be completed,
726+ NSUnderlyingError=0x101285070 {Error Domain=NSOSStatusErrorDomain Code=-12151"(null)"}}
727+```
724728
725729## See also
726730
727731- [[macOS Audio Player]]
728732- [[macOS Audio Recorder]]
729733- [[macOS MIDI Player]]
730734- [[macOS NSSound]]
731735- [[macOS Sound Utilities]]
732736- [[macOS Play Alert Sound|System Sound Services]]
733737- [[Multimedia Programming]]
734738
735739## External links
736740
737741- [Apple: AVFoundation Framework](https://developer.apple.com/documentation/avfoundation)
738742- [Apple: Media Playback and Selection](https://developer.apple.com/documentation/avfoundation/media_playback_and_selection)
739743- [Apple: AVPlayer](https://developer.apple.com/documentation/avfoundation/avplayer)
740744- [Apple: AVAsset](https://developer.apple.com/documentation/avfoundation/avasset)
741745- [Apple: AVPlayerItem](https://developer.apple.com/documentation/avfoundation/avplayeritem)
742746- [Apple: AVPlayerView](https://developer.apple.com/documentation/avkit/avplayerview)
743747- [Apple: AVPlayerLayer](https://developer.apple.com/documentation/avfoundation/avplayerlayer)
744748
745749---
746750*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).*
747751Category: Audio
748752Category: Code
749753Category: Multimedia
750754Category: Video
751755Category: macOS

Back to history