Parcourir la source

Refactored the video support for hashlink using the hlvideo library (#1041)

Maurice Doison il y a 3 ans
Parent
commit
57b89771af
1 fichiers modifiés avec 109 ajouts et 68 suppressions
  1. 109 68
      h2d/Video.hx

+ 109 - 68
h2d/Video.hx

@@ -1,40 +1,17 @@
 package h2d;
 
-#if hl
-private abstract VideoImpl(hl.Abstract<"hl_video">) {
-
-	@:hlNative("video","video_close") public function close() : Void {
-	}
-
-	@:hlNative("video","video_decode_frame") public function decodeFrame( out : hl.Bytes, time : hl.Ref<Float> ) : Bool {
-		return false;
-	}
-
-	@:hlNative("video","video_get_size") public function getSize( width : hl.Ref<Int>, height : hl.Ref<Int> ) : Void {
-	}
-
-	@:hlNative("video","video_open") public static function open( file : hl.Bytes ) : VideoImpl {
-		return null;
-	}
-
-	@:hlNative("video","video_init") public static function init() {
-	}
-
-}
-#end
-
 /**
 	A video file playback Drawable. Due to platform specifics, each target have their own limitations.
 
-	* <span class="label">Hashlink</span>: Playback ability depends on `video` library.
-		At the time of HL 1.11 it's not bundled and have to be [compiled manually](https://github.com/HaxeFoundation/hashlink/tree/master/libs/video) with FFMPEG.
+	* <span class="label">Hashlink</span>: Playback ability depends on `https://github.com/HeapsIO/hlvideo` library. It support only video with the AV1 codec packed into a WEBM container.
+		
 	* <span class="label">JavaScript</span>: HTML Video element will be used. Playback is restricted by content-security policy and browser decoder capabilities.
 **/
 class Video extends Drawable {
 
-	#if hl
-	static var INIT_DONE = false;
-	var v : VideoImpl;
+	#if (hl && hlvideo)
+	var webm : hl.video.Webm;
+	var codec : hl.video.Aom.Codec;
 	var pixels : hxd.Pixels;
 	#elseif js
 	var v : js.html.VideoElement;
@@ -109,7 +86,10 @@ class Video extends Drawable {
 	@:dox(hide) @:noCompletion
 	public function set_loop(value : Bool) : Bool {
 		#if js
-		return v.loop = loopVideo = value;
+		loopVideo = value;
+		if(v != null)
+			v.loop = loopVideo;
+		return loopVideo;
 		#else
 		return loopVideo = value;
 		#end
@@ -119,11 +99,15 @@ class Video extends Drawable {
 		Disposes of the currently playing Video and frees GPU memory.
 	**/
 	public function dispose() {
-		#if hl
-		if( v != null ) {
-			v.close();
-			v = null;
-		};
+		#if (hl && hlvideo)
+		if( webm != null ) {
+			webm.close();
+			webm = null;
+		}
+		if( codec != null ) {
+			codec.close();
+			codec = null;
+		}
 		pixels = null;
 		#elseif js
 		if ( v != null ) {
@@ -148,30 +132,17 @@ class Video extends Drawable {
 	/**
 		Loads and starts the video playback by specified `path` and calls `onReady` when playback becomes possible.
 
-		* <span class="label">Hashlink</span>: Playback being immediately after `load`, unless video was not being able to initialize.
+		* <span class="label">Hashlink</span>: Playback being immediately after `loadFile`, unless video was not being able to initialize.
 		* <span class="label">JavaScript</span>: There won't be any video output until video is properly buffered enough data by the browser, in which case `onReady` is called.
 
 		@param path The video path. Have to be valid file-system path for HL or valid URL (full or relative) for JS.
 		@param onReady An optional callback signalling that video is initialized and began the video playback.
 	**/
-	public function load( path : String, ?onReady : Void -> Void ) {
+	public function loadFile( path : String, ?onReady : Void -> Void ) {
 		dispose();
 
-		#if hl
-		if( !INIT_DONE ) { INIT_DONE = true; VideoImpl.init(); }
-		v = VideoImpl.open(@:privateAccess path.toUtf8());
-		if( v == null ) {
-			onError("Failed to init video " + path);
-			return;
-		}
-		var w = 0, h = 0;
-		v.getSize(w, h);
-		videoWidth = w;
-		videoHeight = h;
-		playing = true;
-		playTime = haxe.Timer.stamp();
-		videoTime = 0.;
-		if( onReady != null ) onReady();
+		#if (hl && hlvideo)
+		webm = hl.video.Webm.fromFile(path);
 		#elseif js
 		v = js.Browser.document.createVideoElement();
 		v.autoplay = true;
@@ -190,6 +161,57 @@ class Video extends Drawable {
 		v.play();
 		#else
 		onError("Video not supported on this platform");
+		return;
+		#end
+		start();
+		if( onReady != null ) onReady();
+	}
+
+	/**
+		Loads and starts the video playback by specified `res` and calls `onReady` when playback becomes possible.
+
+		* <span class="label">Hashlink</span>: Playback being immediately after `loadResource`, unless video was not being able to initialize.
+		* <span class="label">JavaScript</span>: Not implemented
+
+		@param res The heaps resource of a valid video file
+		@param onReady An optional callback signalling that video is initialized and began the video playback.
+	**/
+	public function loadResource( res : hxd.res.Resource, ?onReady : Void -> Void ) {
+		#if (hl && hlvideo)
+		var e = res.entry;
+		webm = hl.video.Webm.fromReader(function(offset : Int, len : Int) {
+			var buf = haxe.io.Bytes.alloc(len);
+			var n = e.readBytes(buf, 0, offset, len);
+			return buf;
+		}, res.entry.size);
+		webm.availableSize = res.entry.size;
+		start();
+		if( onReady != null ) onReady();
+		#else
+		onError("Video from resource not supported on this platform");
+		#end
+	}
+
+	function start() {
+		#if (hl && hlvideo)
+		try {
+			webm.init();
+		} catch(e:Any) {
+			onError("Failed to init video : " + e);
+			return;
+		}
+		codec = webm.createCodec();
+		if(codec == null) {
+			onError("Can't create codec " + webm.videoCodec);
+			return;
+		}
+		var w = 0, h = 0;
+		videoWidth = webm.width;
+		videoHeight = webm.height;
+		playing = true;
+		playTime = haxe.Timer.stamp();
+		videoTime = 0.;
+		loadNextFrame();
 		#end
 	}
 
@@ -228,6 +250,7 @@ class Video extends Drawable {
 				onReady();
 				onReady = null;
 			}
+			loadNextFrame();
 		}
 	}
 	#end
@@ -237,22 +260,47 @@ class Video extends Drawable {
 			ctx.drawTile(this, tile);
 	}
 
-	#if js
-	@:access(h3d.mat.Texture)
-	#end
-	override function sync(ctx:RenderContext) {
-		if( !playing )
-			return;
+	function loadNextFrame() {
 		if( texture == null ) {
 			var w = videoWidth, h = videoHeight;
 			texture = new h3d.mat.Texture(w, h);
 			tile = h2d.Tile.fromTexture(texture);
-			#if hl
+			#if (hl && hlvideo)
 			pixels = new hxd.Pixels(w, h, haxe.io.Bytes.alloc(w * h * 4), h3d.mat.Texture.nativeFormat);
 			#end
 		}
-		if( frameReady ) {
-			#if hl
+		#if (hl && hlvideo)
+		var t : Null<Float>;
+		if((t = webm.readFrame(codec, pixels.bytes)) == null) {
+			if(loopVideo) {
+				webm.rewind();
+				webm.readFrame(codec, pixels.bytes);
+				frameReady = true;
+				playTime = haxe.Timer.stamp();
+				videoTime = 0.;
+			}
+			else {
+				frameReady = false;
+				playing = false;
+				videoTime = 0.;
+			}
+		}
+		else {
+			frameReady = true; // delay decode/upload for more reliable FPS
+			videoTime = t;
+		}
+		#end
+	}
+
+	#if js
+	@:access(h3d.mat.Texture)
+	#end
+	override function sync(ctx:RenderContext) {
+		if( !playing )
+			return;
+		
+		if( frameReady && time >= videoTime ) {
+			#if (hl && hlvideo)
 			texture.uploadPixels(pixels);
 			frameReady = false;
 			#elseif js
@@ -262,15 +310,8 @@ class Video extends Drawable {
 			texture.flags.set(WasCleared);
 			texture.checkMipMapGen(0, 0);
 			#end
+			loadNextFrame();
 		}
-		#if hl
-		if( time >= videoTime ) {
-			var t = 0.;
-			v.decodeFrame(pixels.bytes, t);
-			videoTime = t;
-			frameReady = true; // delay decode/upload for more reliable FPS
-		}
-		#end
 	}
 
 }