mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-27 01:55:52 -05:00
Merge pull request #399 from FunkinCrew/bugfix/html5-video-pausing
Rework HTML5 video cutscenes to properly support the pause menu and volume controls.
This commit is contained in:
commit
99cb7263ca
4 changed files with 153 additions and 26 deletions
|
@ -1,11 +1,8 @@
|
||||||
package funkin.audio;
|
package funkin.audio;
|
||||||
|
|
||||||
#if flash11
|
|
||||||
import flash.media.Sound;
|
|
||||||
import flash.utils.ByteArray;
|
|
||||||
#end
|
|
||||||
import flixel.sound.FlxSound;
|
import flixel.sound.FlxSound;
|
||||||
import flixel.group.FlxGroup.FlxTypedGroup;
|
import flixel.group.FlxGroup.FlxTypedGroup;
|
||||||
|
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||||
import flixel.system.FlxAssets.FlxSoundAsset;
|
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||||
import funkin.util.tools.ICloneable;
|
import funkin.util.tools.ICloneable;
|
||||||
import funkin.data.song.SongData.SongMusicData;
|
import funkin.data.song.SongData.SongMusicData;
|
||||||
|
@ -27,6 +24,25 @@ class FunkinSound extends FlxSound implements ICloneable<FunkinSound>
|
||||||
{
|
{
|
||||||
static final MAX_VOLUME:Float = 1.0;
|
static final MAX_VOLUME:Float = 1.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An FlxSignal which is dispatched when the volume changes.
|
||||||
|
*/
|
||||||
|
public static var onVolumeChanged(get, never):FlxTypedSignal<Float->Void>;
|
||||||
|
|
||||||
|
static var _onVolumeChanged:Null<FlxTypedSignal<Float->Void>> = null;
|
||||||
|
|
||||||
|
static function get_onVolumeChanged():FlxTypedSignal<Float->Void>
|
||||||
|
{
|
||||||
|
if (_onVolumeChanged == null)
|
||||||
|
{
|
||||||
|
_onVolumeChanged = new FlxTypedSignal<Float->Void>();
|
||||||
|
FlxG.sound.volumeHandler = function(volume:Float) {
|
||||||
|
_onVolumeChanged.dispatch(volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _onVolumeChanged;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using `FunkinSound.load` will override a dead instance from here rather than creating a new one, if possible!
|
* Using `FunkinSound.load` will override a dead instance from here rather than creating a new one, if possible!
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -3,6 +3,9 @@ package funkin.graphics;
|
||||||
import flixel.FlxSprite;
|
import flixel.FlxSprite;
|
||||||
import flixel.util.FlxColor;
|
import flixel.util.FlxColor;
|
||||||
import flixel.graphics.FlxGraphic;
|
import flixel.graphics.FlxGraphic;
|
||||||
|
import openfl.display3D.textures.TextureBase;
|
||||||
|
import funkin.graphics.framebuffer.FixedBitmapData;
|
||||||
|
import openfl.display.BitmapData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An FlxSprite with additional functionality.
|
* An FlxSprite with additional functionality.
|
||||||
|
@ -41,7 +44,7 @@ class FunkinSprite extends FlxSprite
|
||||||
*/
|
*/
|
||||||
public static function create(x:Float = 0.0, y:Float = 0.0, key:String):FunkinSprite
|
public static function create(x:Float = 0.0, y:Float = 0.0, key:String):FunkinSprite
|
||||||
{
|
{
|
||||||
var sprite = new FunkinSprite(x, y);
|
var sprite:FunkinSprite = new FunkinSprite(x, y);
|
||||||
sprite.loadTexture(key);
|
sprite.loadTexture(key);
|
||||||
return sprite;
|
return sprite;
|
||||||
}
|
}
|
||||||
|
@ -55,7 +58,7 @@ class FunkinSprite extends FlxSprite
|
||||||
*/
|
*/
|
||||||
public static function createSparrow(x:Float = 0.0, y:Float = 0.0, key:String):FunkinSprite
|
public static function createSparrow(x:Float = 0.0, y:Float = 0.0, key:String):FunkinSprite
|
||||||
{
|
{
|
||||||
var sprite = new FunkinSprite(x, y);
|
var sprite:FunkinSprite = new FunkinSprite(x, y);
|
||||||
sprite.loadSparrow(key);
|
sprite.loadSparrow(key);
|
||||||
return sprite;
|
return sprite;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +72,7 @@ class FunkinSprite extends FlxSprite
|
||||||
*/
|
*/
|
||||||
public static function createPacker(x:Float = 0.0, y:Float = 0.0, key:String):FunkinSprite
|
public static function createPacker(x:Float = 0.0, y:Float = 0.0, key:String):FunkinSprite
|
||||||
{
|
{
|
||||||
var sprite = new FunkinSprite(x, y);
|
var sprite:FunkinSprite = new FunkinSprite(x, y);
|
||||||
sprite.loadPacker(key);
|
sprite.loadPacker(key);
|
||||||
return sprite;
|
return sprite;
|
||||||
}
|
}
|
||||||
|
@ -89,6 +92,30 @@ class FunkinSprite extends FlxSprite
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply an OpenFL `BitmapData` to this sprite.
|
||||||
|
* @param input The OpenFL `BitmapData` to apply
|
||||||
|
* @return This sprite, for chaining
|
||||||
|
*/
|
||||||
|
public function loadBitmapData(input:BitmapData):FunkinSprite
|
||||||
|
{
|
||||||
|
loadGraphic(input);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply an OpenFL `TextureBase` to this sprite.
|
||||||
|
* @param input The OpenFL `TextureBase` to apply
|
||||||
|
* @return This sprite, for chaining
|
||||||
|
*/
|
||||||
|
public function loadTextureBase(input:TextureBase):FunkinSprite
|
||||||
|
{
|
||||||
|
var inputBitmap:FixedBitmapData = FixedBitmapData.fromTexture(input);
|
||||||
|
|
||||||
|
return loadBitmapData(inputBitmap);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load an animated texture (Sparrow atlas spritesheet) as the sprite's texture.
|
* Load an animated texture (Sparrow atlas spritesheet) as the sprite's texture.
|
||||||
* @param key The key of the texture to load.
|
* @param key The key of the texture to load.
|
||||||
|
@ -119,11 +146,20 @@ class FunkinSprite extends FlxSprite
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the texture with the given key is cached.
|
||||||
|
* @param key The key of the texture to check.
|
||||||
|
* @return Whether the texture is cached.
|
||||||
|
*/
|
||||||
public static function isTextureCached(key:String):Bool
|
public static function isTextureCached(key:String):Bool
|
||||||
{
|
{
|
||||||
return FlxG.bitmap.get(key) != null;
|
return FlxG.bitmap.get(key) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the texture with the given key is cached.
|
||||||
|
* @param key The key of the texture to cache.
|
||||||
|
*/
|
||||||
public static function cacheTexture(key:String):Void
|
public static function cacheTexture(key:String):Void
|
||||||
{
|
{
|
||||||
// We don't want to cache the same texture twice.
|
// We don't want to cache the same texture twice.
|
||||||
|
@ -139,7 +175,7 @@ class FunkinSprite extends FlxSprite
|
||||||
}
|
}
|
||||||
|
|
||||||
// Else, texture is currently uncached.
|
// Else, texture is currently uncached.
|
||||||
var graphic = flixel.graphics.FlxGraphic.fromAssetKey(key, false, null, true);
|
var graphic:FlxGraphic = FlxGraphic.fromAssetKey(key, false, null, true);
|
||||||
if (graphic == null)
|
if (graphic == null)
|
||||||
{
|
{
|
||||||
FlxG.log.warn('Failed to cache graphic: $key');
|
FlxG.log.warn('Failed to cache graphic: $key');
|
||||||
|
|
|
@ -32,11 +32,11 @@ class FixedBitmapData extends BitmapData
|
||||||
public static function fromTexture(texture:TextureBase):FixedBitmapData
|
public static function fromTexture(texture:TextureBase):FixedBitmapData
|
||||||
{
|
{
|
||||||
if (texture == null) return null;
|
if (texture == null) return null;
|
||||||
final bitmapData = new FixedBitmapData(texture.__width, texture.__height, true, 0);
|
final bitmapData:FixedBitmapData = new FixedBitmapData(texture.__width, texture.__height, true, 0);
|
||||||
bitmapData.readable = false;
|
// bitmapData.readable = false;
|
||||||
bitmapData.__texture = texture;
|
bitmapData.__texture = texture;
|
||||||
bitmapData.__textureContext = texture.__textureContext;
|
bitmapData.__textureContext = texture.__textureContext;
|
||||||
bitmapData.image = null;
|
// bitmapData.image = null;
|
||||||
return bitmapData;
|
return bitmapData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,45 +1,58 @@
|
||||||
package funkin.graphics.video;
|
package funkin.graphics.video;
|
||||||
|
|
||||||
import flixel.FlxBasic;
|
import flixel.util.FlxColor;
|
||||||
import flixel.FlxSprite;
|
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||||
|
import funkin.audio.FunkinSound;
|
||||||
|
import openfl.display3D.textures.TextureBase;
|
||||||
import openfl.events.NetStatusEvent;
|
import openfl.events.NetStatusEvent;
|
||||||
|
import openfl.media.SoundTransform;
|
||||||
import openfl.media.Video;
|
import openfl.media.Video;
|
||||||
import openfl.net.NetConnection;
|
import openfl.net.NetConnection;
|
||||||
import openfl.net.NetStream;
|
import openfl.net.NetStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays a video via a NetStream. Only works on HTML5.
|
* Plays a video via a NetStream. Only works on HTML5.
|
||||||
* This does NOT replace hxCodec, nor does hxCodec replace this. hxCodec only works on desktop and does not work on HTML5!
|
* This does NOT replace hxCodec, nor does hxCodec replace this.
|
||||||
|
* hxCodec only works on desktop and does not work on HTML5!
|
||||||
*/
|
*/
|
||||||
class FlxVideo extends FlxBasic
|
class FlxVideo extends FunkinSprite
|
||||||
{
|
{
|
||||||
var video:Video;
|
var video:Video;
|
||||||
var netStream:NetStream;
|
var netStream:NetStream;
|
||||||
|
var videoPath:String;
|
||||||
public var finishCallback:Void->Void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Doesn't actually interact with Flixel shit, only just a pleasant to use class
|
* A callback to execute when the video finishes.
|
||||||
*/
|
*/
|
||||||
|
public var finishCallback:Void->Void;
|
||||||
|
|
||||||
public function new(videoPath:String)
|
public function new(videoPath:String)
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.videoPath = videoPath;
|
||||||
|
|
||||||
|
makeGraphic(2, 2, FlxColor.TRANSPARENT);
|
||||||
|
|
||||||
video = new Video();
|
video = new Video();
|
||||||
video.x = 0;
|
video.x = 0;
|
||||||
video.y = 0;
|
video.y = 0;
|
||||||
|
video.alpha = 0;
|
||||||
|
|
||||||
FlxG.addChildBelowMouse(video);
|
FlxG.game.addChild(video);
|
||||||
|
|
||||||
var netConnection = new NetConnection();
|
var netConnection:NetConnection = new NetConnection();
|
||||||
netConnection.connect(null);
|
netConnection.connect(null);
|
||||||
|
|
||||||
netStream = new NetStream(netConnection);
|
netStream = new NetStream(netConnection);
|
||||||
netStream.client = {onMetaData: client_onMetaData};
|
netStream.client = {onMetaData: onClientMetaData};
|
||||||
netConnection.addEventListener(NetStatusEvent.NET_STATUS, netConnection_onNetStatus);
|
netConnection.addEventListener(NetStatusEvent.NET_STATUS, onNetConnectionNetStatus);
|
||||||
netStream.play(videoPath);
|
netStream.play(videoPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the FlxVideo to pause playback.
|
||||||
|
*/
|
||||||
public function pauseVideo():Void
|
public function pauseVideo():Void
|
||||||
{
|
{
|
||||||
if (netStream != null)
|
if (netStream != null)
|
||||||
|
@ -48,6 +61,9 @@ class FlxVideo extends FlxBasic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the FlxVideo to resume if it is paused.
|
||||||
|
*/
|
||||||
public function resumeVideo():Void
|
public function resumeVideo():Void
|
||||||
{
|
{
|
||||||
// Resume playing the video.
|
// Resume playing the video.
|
||||||
|
@ -57,6 +73,29 @@ class FlxVideo extends FlxBasic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var videoAvailable:Bool = false;
|
||||||
|
var frameTimer:Float;
|
||||||
|
|
||||||
|
static final FRAME_RATE:Float = 60;
|
||||||
|
|
||||||
|
public override function update(elapsed:Float):Void
|
||||||
|
{
|
||||||
|
super.update(elapsed);
|
||||||
|
|
||||||
|
if (frameTimer >= (1 / FRAME_RATE))
|
||||||
|
{
|
||||||
|
frameTimer = 0;
|
||||||
|
// TODO: We just draw the video buffer to the sprite 60 times a second.
|
||||||
|
// Can we copy the video buffer instead somehow?
|
||||||
|
pixels.draw(video);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoAvailable) frameTimer += elapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the FlxVideo to seek to the beginning.
|
||||||
|
*/
|
||||||
public function restartVideo():Void
|
public function restartVideo():Void
|
||||||
{
|
{
|
||||||
// Seek to the beginning of the video.
|
// Seek to the beginning of the video.
|
||||||
|
@ -66,6 +105,9 @@ class FlxVideo extends FlxBasic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the FlxVideo to end.
|
||||||
|
*/
|
||||||
public function finishVideo():Void
|
public function finishVideo():Void
|
||||||
{
|
{
|
||||||
netStream.dispose();
|
netStream.dispose();
|
||||||
|
@ -74,15 +116,48 @@ class FlxVideo extends FlxBasic
|
||||||
if (finishCallback != null) finishCallback();
|
if (finishCallback != null) finishCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function client_onMetaData(metaData:Dynamic)
|
public override function destroy():Void
|
||||||
|
{
|
||||||
|
if (netStream != null)
|
||||||
|
{
|
||||||
|
netStream.dispose();
|
||||||
|
|
||||||
|
if (FlxG.game.contains(video)) FlxG.game.removeChild(video);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback executed when the video stream loads.
|
||||||
|
* @param metaData The metadata of the video
|
||||||
|
*/
|
||||||
|
public function onClientMetaData(metaData:Dynamic):Void
|
||||||
{
|
{
|
||||||
video.attachNetStream(netStream);
|
video.attachNetStream(netStream);
|
||||||
|
|
||||||
video.width = FlxG.width;
|
onVideoReady();
|
||||||
video.height = FlxG.height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function netConnection_onNetStatus(event:NetStatusEvent):Void
|
function onVideoReady():Void
|
||||||
|
{
|
||||||
|
video.width = FlxG.width;
|
||||||
|
video.height = FlxG.height;
|
||||||
|
|
||||||
|
videoAvailable = true;
|
||||||
|
|
||||||
|
FunkinSound.onVolumeChanged.add(onVolumeChanged);
|
||||||
|
onVolumeChanged(FlxG.sound.muted ? 0 : FlxG.sound.volume);
|
||||||
|
|
||||||
|
makeGraphic(Std.int(video.width), Std.int(video.height), FlxColor.TRANSPARENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onVolumeChanged(volume:Float):Void
|
||||||
|
{
|
||||||
|
netStream.soundTransform = new SoundTransform(volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNetConnectionNetStatus(event:NetStatusEvent):Void
|
||||||
{
|
{
|
||||||
if (event.info.code == 'NetStream.Play.Complete') finishVideo();
|
if (event.info.code == 'NetStream.Play.Complete') finishVideo();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue