mirror of
https://github.com/Heretek-AI/GDevelop.git
synced 2026-07-24 20:05:39 -04:00
deb802cfec
* For now, this is limited to instances inside custom objects. This will be made available in the future for all instances in scenes if this works well.
458 lines
12 KiB
TypeScript
458 lines
12 KiB
TypeScript
namespace gdjs {
|
|
/**
|
|
* The initial properties for {@link gdjs.VideoRuntimeObject}
|
|
* @category Objects > Video
|
|
*/
|
|
export type VideoObjectDataType = {
|
|
/** The base parameters of the video */
|
|
content: {
|
|
/** The opacity of the video */
|
|
opacity: number;
|
|
/** Does the video loops itself? */
|
|
loop: boolean;
|
|
/** The volume of the video */
|
|
volume: number;
|
|
/** Name of the resource corresponding to the video */
|
|
videoResource: string;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @category Objects > Video
|
|
*/
|
|
export type VideoObjectData = ObjectData & VideoObjectDataType;
|
|
|
|
/**
|
|
* @category Objects > Video
|
|
*/
|
|
export type VideoObjectNetworkSyncDataType = {
|
|
op: float;
|
|
// We don't sync volume, as it's probably a user setting?
|
|
pla: boolean;
|
|
loop: boolean;
|
|
ct: float;
|
|
ps: number;
|
|
};
|
|
|
|
/**
|
|
* @category Objects > Video
|
|
*/
|
|
export type VideoObjectNetworkSyncData = ObjectNetworkSyncData &
|
|
VideoObjectNetworkSyncDataType;
|
|
|
|
/**
|
|
* An object displaying a video on screen.
|
|
*
|
|
* For the same video resource, only one video is being created in memory (
|
|
* as a HTMLVideoElement). This means that two objects displaying the same
|
|
* video will have the same state for this video (paused/playing, current time,
|
|
* volume, etc...).
|
|
* @category Objects > Video
|
|
*/
|
|
export class VideoRuntimeObject
|
|
extends gdjs.RuntimeObject
|
|
implements gdjs.OpacityHandler
|
|
{
|
|
_opacity: float;
|
|
_loop: boolean;
|
|
_volume: float;
|
|
_videoResource: string;
|
|
|
|
// Use a boolean to track if the video was paused because we
|
|
// navigated to another scene, and so should resume if we're back.
|
|
_pausedAsScenePaused: boolean = false;
|
|
_renderer: gdjs.VideoRuntimeObjectRenderer;
|
|
_playbackSpeed: any;
|
|
|
|
/**
|
|
* @param instanceContainer The scene the object belongs to.
|
|
* @param videoObjectData The data defining the object
|
|
*/
|
|
constructor(
|
|
instanceContainer: gdjs.RuntimeInstanceContainer,
|
|
videoObjectData: VideoObjectData,
|
|
instanceData?: InstanceData
|
|
) {
|
|
super(instanceContainer, videoObjectData, instanceData);
|
|
this._opacity = videoObjectData.content.opacity;
|
|
this._loop = videoObjectData.content.loop;
|
|
this._volume = videoObjectData.content.volume;
|
|
this._videoResource = videoObjectData.content.videoResource;
|
|
this._renderer = new gdjs.VideoRuntimeObjectRenderer(
|
|
this,
|
|
instanceContainer
|
|
);
|
|
|
|
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
|
this.onCreated();
|
|
}
|
|
|
|
getRendererObject() {
|
|
return this._renderer.getRendererObject();
|
|
}
|
|
|
|
updateFromObjectData(
|
|
oldObjectData: VideoObjectData,
|
|
newObjectData: VideoObjectData
|
|
): boolean {
|
|
if (oldObjectData.content.opacity !== newObjectData.content.opacity) {
|
|
this.setOpacity(newObjectData.content.opacity);
|
|
}
|
|
if (oldObjectData.content.loop !== newObjectData.content.loop) {
|
|
this.setLoop(newObjectData.content.loop);
|
|
}
|
|
if (oldObjectData.content.volume !== newObjectData.content.volume) {
|
|
this.setVolume(newObjectData.content.volume);
|
|
}
|
|
if (
|
|
oldObjectData.content.videoResource !==
|
|
newObjectData.content.videoResource
|
|
) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
getNetworkSyncData(
|
|
syncOptions: GetNetworkSyncDataOptions
|
|
): VideoObjectNetworkSyncData {
|
|
return {
|
|
...super.getNetworkSyncData(syncOptions),
|
|
op: this._opacity,
|
|
pla: this.isPlayed(),
|
|
loop: this.isLooped(),
|
|
ct: this.getCurrentTime(),
|
|
ps: this.getPlaybackSpeed(),
|
|
};
|
|
}
|
|
|
|
updateFromNetworkSyncData(
|
|
syncData: VideoObjectNetworkSyncData,
|
|
options: UpdateFromNetworkSyncDataOptions
|
|
): void {
|
|
super.updateFromNetworkSyncData(syncData, options);
|
|
|
|
if (this._opacity !== undefined && this._opacity && syncData.op) {
|
|
this.setOpacity(syncData.op);
|
|
}
|
|
if (syncData.pla !== undefined && this.isPlayed() !== syncData.pla) {
|
|
syncData.pla ? this.play() : this.pause();
|
|
}
|
|
if (syncData.loop !== undefined && this.isLooped() !== syncData.loop) {
|
|
this.setLoop(syncData.loop);
|
|
}
|
|
// We don't update the current time too regularly, only if it's off by a lot.
|
|
if (
|
|
syncData.ct !== undefined &&
|
|
Math.abs(this.getCurrentTime() - syncData.ct) > 3 // More than 3 seconds off
|
|
) {
|
|
this.setCurrentTime(syncData.ct);
|
|
}
|
|
if (
|
|
syncData.ps !== undefined &&
|
|
this.getPlaybackSpeed() !== syncData.ps
|
|
) {
|
|
this.setPlaybackSpeed(syncData.ps);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize the extra parameters that could be set for an instance.
|
|
* @param initialInstanceData The initial instance data
|
|
*/
|
|
extraInitializationFromInitialInstance(initialInstanceData: InstanceData) {
|
|
if (initialInstanceData.customSize) {
|
|
this.setWidth(initialInstanceData.width);
|
|
this.setHeight(initialInstanceData.height);
|
|
}
|
|
this.setOpacity(
|
|
initialInstanceData.opacity === undefined
|
|
? 255
|
|
: initialInstanceData.opacity
|
|
);
|
|
}
|
|
|
|
onDestroyed(): void {
|
|
super.onDestroyed();
|
|
this._renderer.onDestroy();
|
|
}
|
|
|
|
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
|
this._renderer.ensureUpToDate();
|
|
}
|
|
|
|
/**
|
|
* Set object position on X axis.
|
|
* @param x The new position X of the object.
|
|
*/
|
|
setX(x: float): void {
|
|
super.setX(x);
|
|
this._renderer.updatePosition();
|
|
}
|
|
|
|
/**
|
|
* Set object position on Y axis.
|
|
* @param y The new position Y of the object.
|
|
*/
|
|
setY(y: float): void {
|
|
super.setY(y);
|
|
this._renderer.updatePosition();
|
|
}
|
|
|
|
/**
|
|
* Set the angle of the object.
|
|
* @param angle The new angle of the object.
|
|
*/
|
|
setAngle(angle: float): void {
|
|
super.setAngle(angle);
|
|
this._renderer.updateAngle();
|
|
}
|
|
|
|
/**
|
|
* Set object opacity.
|
|
* @param opacity The new opacity of the object (0-255).
|
|
*/
|
|
setOpacity(opacity: float): void {
|
|
this._opacity = opacity;
|
|
this._renderer.updateOpacity();
|
|
}
|
|
|
|
/**
|
|
* Get object opacity.
|
|
* @returns The current opacity
|
|
*/
|
|
getOpacity(): number {
|
|
return this._opacity;
|
|
}
|
|
|
|
/**
|
|
* Set the width of the video.
|
|
* @param width The new width in pixels.
|
|
*/
|
|
setWidth(width: float): void {
|
|
if (this._renderer.getWidth() === width) return;
|
|
|
|
this._renderer.setWidth(width);
|
|
this.invalidateHitboxes();
|
|
}
|
|
|
|
/**
|
|
* Set the height of the video.
|
|
* @param height The new height in pixels.
|
|
*/
|
|
setHeight(height: float): void {
|
|
if (this._renderer.getHeight() === height) return;
|
|
|
|
this._renderer.setHeight(height);
|
|
this.invalidateHitboxes();
|
|
}
|
|
|
|
/**
|
|
* Get the width of the video object.
|
|
* @returns The current width of the object
|
|
*/
|
|
getWidth(): float {
|
|
return this._renderer.getWidth();
|
|
}
|
|
|
|
/**
|
|
* Get the height of the video object.
|
|
* @returns The current height of the object
|
|
*/
|
|
getHeight(): float {
|
|
return this._renderer.getHeight();
|
|
}
|
|
|
|
/**
|
|
* Play the video.
|
|
*/
|
|
play(): void {
|
|
this._renderer.play();
|
|
}
|
|
|
|
/**
|
|
* Pause the video.
|
|
*/
|
|
pause(): void {
|
|
this._renderer.pause();
|
|
}
|
|
|
|
/**
|
|
* Set the state looped of the video.
|
|
* @param enable true to loop the video
|
|
*/
|
|
setLoop(enable: boolean): void {
|
|
this._renderer.setLoop(enable);
|
|
}
|
|
|
|
/**
|
|
* Set the state muted of the video.
|
|
* @param enable The new state.
|
|
*/
|
|
mute(enable: boolean) {
|
|
this._renderer.setMute(enable);
|
|
}
|
|
|
|
/**
|
|
* Return the state muted of video object.
|
|
* @returns Is the video muted?
|
|
*/
|
|
isMuted(): boolean {
|
|
return this._renderer.isMuted();
|
|
}
|
|
|
|
/**
|
|
* Set the volume of the video object.
|
|
* @param volume The new volume, between 0 and 100.
|
|
*/
|
|
setVolume(volume: number): void {
|
|
this._volume =
|
|
gdjs.evtTools.common.clamp(
|
|
gdjs.evtTools.common.normalize(volume, 0, 100),
|
|
0,
|
|
1
|
|
) * 100;
|
|
this._renderer.updateVolume();
|
|
}
|
|
|
|
/**
|
|
* Get the volume of the video object.
|
|
* @returns The current video's volume, between 0 and 100.
|
|
*/
|
|
getVolume(): number {
|
|
return (
|
|
gdjs.evtTools.common.normalize(this._renderer.getVolume(), 0, 1) * 100
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check if the video is being played.
|
|
* @returns Is the video being played?
|
|
*/
|
|
isPlayed(): boolean {
|
|
return this._renderer.isPlayed();
|
|
}
|
|
|
|
/**
|
|
* Check if the video is paused.
|
|
* @returns Is the video being paused?
|
|
*/
|
|
isPaused(): boolean {
|
|
return !this._renderer.isPlayed();
|
|
}
|
|
|
|
/**
|
|
* Check if the video is looping.
|
|
* @returns Is the video looping?
|
|
*/
|
|
isLooped(): boolean {
|
|
return this._renderer.isLooped();
|
|
}
|
|
|
|
/**
|
|
* Return the total time of the video.
|
|
* @returns The duration of the video
|
|
*/
|
|
getDuration(): number {
|
|
return this._renderer.getDuration();
|
|
}
|
|
|
|
/**
|
|
* Check if the video has ended.
|
|
* @returns Has the video ended?
|
|
*/
|
|
isEnded(): boolean {
|
|
return this._renderer.isEnded();
|
|
}
|
|
|
|
/**
|
|
* Set the new time of the video object.
|
|
* @param time The new time.
|
|
*/
|
|
setCurrentTime(time: float): void {
|
|
this._renderer.setCurrentTime(time);
|
|
}
|
|
|
|
/**
|
|
* Get the current time of the video object.
|
|
* @returns The current time of the video
|
|
*/
|
|
getCurrentTime(): float {
|
|
return this._renderer.getCurrentTime();
|
|
}
|
|
|
|
/**
|
|
* Set the new playback speed of the video object.
|
|
* @param playbackSpeed The new playback speed.
|
|
*/
|
|
setPlaybackSpeed(playbackSpeed: number): void {
|
|
this._playbackSpeed = gdjs.evtTools.common.clamp(playbackSpeed, 0.5, 2);
|
|
this._renderer.setPlaybackSpeed(this._playbackSpeed);
|
|
}
|
|
|
|
/**
|
|
* Get the playback speed of the video object.
|
|
* @returns The current playback speed of the video.
|
|
*/
|
|
getPlaybackSpeed(): number {
|
|
return this._renderer.getPlaybackSpeed();
|
|
}
|
|
}
|
|
gdjs.registerObject('Video::VideoObject', gdjs.VideoRuntimeObject);
|
|
|
|
/**
|
|
* When a scene is unloaded, pause any video being run.
|
|
* TODO: Investigate how to dispose the video source?
|
|
*/
|
|
gdjs.registerRuntimeSceneUnloadedCallback(function (runtimeScene) {
|
|
// Manually find all the gdjs.VideoRuntimeObject living on the scene,
|
|
// and pause them.
|
|
const instances = runtimeScene.getAdhocListOfAllInstances();
|
|
for (let i = 0; i < instances.length; ++i) {
|
|
const obj = instances[i];
|
|
if (obj instanceof gdjs.VideoRuntimeObject) {
|
|
if (obj.isPlayed()) {
|
|
obj.pause();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* When a scene is paused, pause any video being run.
|
|
*/
|
|
gdjs.registerRuntimeScenePausedCallback(function (runtimeScene) {
|
|
// Manually find all the gdjs.VideoRuntimeObject living on the scene,
|
|
// and pause them.
|
|
const instances = runtimeScene.getAdhocListOfAllInstances();
|
|
for (let i = 0; i < instances.length; ++i) {
|
|
const obj = instances[i];
|
|
if (obj instanceof gdjs.VideoRuntimeObject) {
|
|
if (obj.isPlayed()) {
|
|
obj.pause();
|
|
obj._pausedAsScenePaused = true;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Flag it to be started again when scene is resumed.
|
|
|
|
/**
|
|
* When a scene is resumed, resume any video previously paused.
|
|
*/
|
|
gdjs.registerRuntimeSceneResumedCallback(function (runtimeScene) {
|
|
// Manually find all the gdjs.VideoRuntimeObject living on the scene,
|
|
// and play them if they have been previously paused.
|
|
const instances = runtimeScene.getAdhocListOfAllInstances();
|
|
for (let i = 0; i < instances.length; ++i) {
|
|
const obj = instances[i];
|
|
if (obj instanceof gdjs.VideoRuntimeObject) {
|
|
if (obj._pausedAsScenePaused) {
|
|
obj.play();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|