diff --git a/doc/classes/VideoStream.xml b/doc/classes/VideoStream.xml
index 2797ad3513ce..648c3edd739c 100644
--- a/doc/classes/VideoStream.xml
+++ b/doc/classes/VideoStream.xml
@@ -8,4 +8,18 @@
+
+
+
+
+ Called when the video starts playing, to initialize and return a subclass of [VideoStreamPlayback].
+
+
+
+
+
+ The video file path or URI that this [VideoStream] resource handles.
+ For [VideoStreamTheora], this filename should be an Ogg Theora video file with the [code].ogv[/code] extension.
+
+
diff --git a/doc/classes/VideoStreamPlayback.xml b/doc/classes/VideoStreamPlayback.xml
new file mode 100644
index 000000000000..8d8b4fe5b112
--- /dev/null
+++ b/doc/classes/VideoStreamPlayback.xml
@@ -0,0 +1,104 @@
+
+
+
+ Internal class used by [VideoStream] to manage playback state when played from a [VideoStreamPlayer].
+
+
+ This class is intended to be overridden by video decoder extensions with custom implementations of [VideoStream].
+
+
+
+
+
+
+
+ Returns the number of audio channels.
+
+
+
+
+
+ Returns the video duration in seconds, if known, or 0 if unknown.
+
+
+
+
+
+ Returns the audio sample rate used for mixing.
+
+
+
+
+
+ Return the current playback timestamp. Called in response to the [member VideoStreamPlayer.stream_position] getter.
+
+
+
+
+
+ Allocates a [Texture2D] in which decoded video frames will be drawn.
+
+
+
+
+
+ Returns the paused status, as set by [method _set_paused].
+
+
+
+
+
+ Returns the playback state, as determined by calls to [method _play] and [method _stop].
+
+
+
+
+
+ Called in response to [member VideoStreamPlayer.autoplay] or [method VideoStreamPlayer.play]. Note that manual playback may also invoke [method _stop] multiple times before this method is called. [method _is_playing] should return true once playing.
+
+
+
+
+
+
+ Seeks to [code]time[/code] seconds. Called in response to the [member VideoStreamPlayer.stream_position] setter.
+
+
+
+
+
+
+ Select the audio track [code]idx[/code]. Called when playback starts, and in response to the [member VideoStreamPlayer.audio_track] setter.
+
+
+
+
+
+
+ Set the paused status of video playback. [method _is_paused] must return [code]paused[/code]. Called in response to the [member VideoStreamPlayer.paused] setter.
+
+
+
+
+
+ Stops playback. May be called multiple times before [method _play], or in response to [method VideoStreamPlayer.stop]. [method _is_playing] should return false once stopped.
+
+
+
+
+
+
+ Ticks video playback for [code]delta[/code] seconds. Called every frame as long as [method _is_paused] and [method _is_playing] return true.
+
+
+
+
+
+
+
+
+ Render [code]num_frames[/code] audio frames (of [method _get_channels] floats each) from [code]buffer[/code], starting from index [code]offset[/code] in the array. Returns the number of audio frames rendered, or -1 on error.
+
+
+
+
diff --git a/modules/theora/doc_classes/VideoStreamTheora.xml b/modules/theora/doc_classes/VideoStreamTheora.xml
index e07af8f16926..3ec762a8805a 100644
--- a/modules/theora/doc_classes/VideoStreamTheora.xml
+++ b/modules/theora/doc_classes/VideoStreamTheora.xml
@@ -9,19 +9,4 @@
-
-
-
-
- Returns the Ogg Theora video file handled by this [VideoStreamTheora].
-
-
-
-
-
-
- Sets the Ogg Theora video file that this [VideoStreamTheora] resource handles. The [code]file[/code] name should have the [code].ogv[/code] extension.
-
-
-
diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp
index c600924a3e9c..b38f7225a277 100644
--- a/modules/theora/video_stream_theora.cpp
+++ b/modules/theora/video_stream_theora.cpp
@@ -574,25 +574,10 @@ bool VideoStreamPlaybackTheora::is_paused() const {
return paused;
}
-void VideoStreamPlaybackTheora::set_loop(bool p_enable) {
-}
-
-bool VideoStreamPlaybackTheora::has_loop() const {
- return false;
-}
-
double VideoStreamPlaybackTheora::get_length() const {
return 0;
}
-String VideoStreamPlaybackTheora::get_stream_name() const {
- return "";
-}
-
-int VideoStreamPlaybackTheora::get_loop_count() const {
- return 0;
-}
-
double VideoStreamPlaybackTheora::get_playback_position() const {
return get_time();
}
@@ -601,11 +586,6 @@ void VideoStreamPlaybackTheora::seek(double p_time) {
WARN_PRINT_ONCE("Seeking in Theora videos is not implemented yet (it's only supported for GDExtension-provided video streams).");
}
-void VideoStreamPlaybackTheora::set_mix_callback(AudioMixCallback p_callback, void *p_userdata) {
- mix_callback = p_callback;
- mix_udata = p_userdata;
-}
-
int VideoStreamPlaybackTheora::get_channels() const {
return vi.channels;
}
@@ -657,16 +637,9 @@ VideoStreamPlaybackTheora::~VideoStreamPlaybackTheora() {
memdelete(thread_sem);
#endif
clear();
-}
+};
-void VideoStreamTheora::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_file", "file"), &VideoStreamTheora::set_file);
- ClassDB::bind_method(D_METHOD("get_file"), &VideoStreamTheora::get_file);
-
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "file", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_file", "get_file");
-}
-
-////////////
+void VideoStreamTheora::_bind_methods() {}
Ref ResourceFormatLoaderTheora::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
Ref f = FileAccess::open(p_path, FileAccess::READ);
diff --git a/modules/theora/video_stream_theora.h b/modules/theora/video_stream_theora.h
index f047440df7aa..32adc28a884f 100644
--- a/modules/theora/video_stream_theora.h
+++ b/modules/theora/video_stream_theora.h
@@ -99,8 +99,6 @@ class VideoStreamPlaybackTheora : public VideoStreamPlayback {
Ref texture;
- AudioMixCallback mix_callback = nullptr;
- void *mix_udata = nullptr;
bool paused = false;
#ifdef THEORA_USE_THREAD_STREAMING
@@ -133,15 +131,8 @@ public:
virtual void set_paused(bool p_paused) override;
virtual bool is_paused() const override;
- virtual void set_loop(bool p_enable) override;
- virtual bool has_loop() const override;
-
virtual double get_length() const override;
- virtual String get_stream_name() const;
-
- virtual int get_loop_count() const;
-
virtual double get_playback_position() const override;
virtual void seek(double p_time) override;
@@ -150,7 +141,6 @@ public:
virtual Ref get_texture() const override;
virtual void update(double p_delta) override;
- virtual void set_mix_callback(AudioMixCallback p_callback, void *p_userdata) override;
virtual int get_channels() const override;
virtual int get_mix_rate() const override;
@@ -163,9 +153,6 @@ public:
class VideoStreamTheora : public VideoStream {
GDCLASS(VideoStreamTheora, VideoStream);
- String file;
- int audio_track;
-
protected:
static void _bind_methods();
@@ -177,8 +164,6 @@ public:
return pb;
}
- void set_file(const String &p_file) { file = p_file; }
- String get_file() { return file; }
void set_audio_track(int p_track) override { audio_track = p_track; }
VideoStreamTheora() { audio_track = 0; }
diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp
index 6eb25bf852f3..1f3bbff779e4 100644
--- a/scene/gui/video_stream_player.cpp
+++ b/scene/gui/video_stream_player.cpp
@@ -236,7 +236,6 @@ void VideoStreamPlayer::set_stream(const Ref &p_stream) {
AudioServer::get_singleton()->unlock();
if (!playback.is_null()) {
- playback->set_loop(loops);
playback->set_paused(paused);
texture = playback->get_texture();
@@ -344,6 +343,12 @@ int VideoStreamPlayer::get_buffering_msec() const {
void VideoStreamPlayer::set_audio_track(int p_track) {
audio_track = p_track;
+ if (stream.is_valid()) {
+ stream->set_audio_track(audio_track);
+ }
+ if (playback.is_valid()) {
+ playback->set_audio_track(audio_track);
+ }
}
int VideoStreamPlayer::get_audio_track() const {
diff --git a/scene/gui/video_stream_player.h b/scene/gui/video_stream_player.h
index 09ef272a9ad6..1fd599a9e1f3 100644
--- a/scene/gui/video_stream_player.h
+++ b/scene/gui/video_stream_player.h
@@ -65,7 +65,6 @@ class VideoStreamPlayer : public Control {
float volume = 1.0;
double last_audio_time = 0.0;
bool expand = false;
- bool loops = false;
int buffering_ms = 500;
int audio_track = 0;
int bus_index = 0;
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 7bebf1cfd3b3..39fc03f9f16b 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -393,6 +393,8 @@ void register_scene_types() {
GDREGISTER_CLASS(LineEdit);
GDREGISTER_CLASS(VideoStreamPlayer);
+ GDREGISTER_VIRTUAL_CLASS(VideoStreamPlayback);
+ GDREGISTER_VIRTUAL_CLASS(VideoStream);
#ifndef ADVANCED_GUI_DISABLED
GDREGISTER_CLASS(FileDialog);
@@ -906,7 +908,6 @@ void register_scene_types() {
#ifndef _3D_DISABLED
GDREGISTER_CLASS(AudioStreamPlayer3D);
#endif
- GDREGISTER_ABSTRACT_CLASS(VideoStream);
GDREGISTER_CLASS(AudioStreamWAV);
GDREGISTER_CLASS(AudioStreamPolyphonic);
GDREGISTER_ABSTRACT_CLASS(AudioStreamPlaybackPolyphonic);
diff --git a/scene/resources/video_stream.cpp b/scene/resources/video_stream.cpp
new file mode 100644
index 000000000000..ee1a47c338d6
--- /dev/null
+++ b/scene/resources/video_stream.cpp
@@ -0,0 +1,198 @@
+/**************************************************************************/
+/* video_stream.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "video_stream.h"
+
+#include "core/config/project_settings.h"
+#include "servers/audio_server.h"
+
+// VideoStreamPlayback starts here.
+
+void VideoStreamPlayback::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("mix_audio", "num_frames", "buffer", "offset"), &VideoStreamPlayback::mix_audio, DEFVAL(PackedFloat32Array()), DEFVAL(0));
+ GDVIRTUAL_BIND(_stop);
+ GDVIRTUAL_BIND(_play);
+ GDVIRTUAL_BIND(_is_playing);
+ GDVIRTUAL_BIND(_set_paused, "paused");
+ GDVIRTUAL_BIND(_is_paused);
+ GDVIRTUAL_BIND(_get_length);
+ GDVIRTUAL_BIND(_get_playback_position);
+ GDVIRTUAL_BIND(_seek, "time");
+ GDVIRTUAL_BIND(_set_audio_track, "idx");
+ GDVIRTUAL_BIND(_get_texture);
+ GDVIRTUAL_BIND(_update, "delta");
+ GDVIRTUAL_BIND(_get_channels);
+ GDVIRTUAL_BIND(_get_mix_rate);
+}
+
+VideoStreamPlayback::VideoStreamPlayback() {
+}
+
+VideoStreamPlayback::~VideoStreamPlayback() {
+}
+
+void VideoStreamPlayback::stop() {
+ GDVIRTUAL_CALL(_stop);
+}
+
+void VideoStreamPlayback::play() {
+ GDVIRTUAL_CALL(_play);
+}
+
+bool VideoStreamPlayback::is_playing() const {
+ bool ret;
+ if (GDVIRTUAL_CALL(_is_playing, ret)) {
+ return ret;
+ }
+ return false;
+}
+
+void VideoStreamPlayback::set_paused(bool p_paused) {
+ GDVIRTUAL_CALL(_is_playing, p_paused);
+}
+
+bool VideoStreamPlayback::is_paused() const {
+ bool ret;
+ if (GDVIRTUAL_CALL(_is_paused, ret)) {
+ return ret;
+ }
+ return false;
+}
+
+double VideoStreamPlayback::get_length() const {
+ double ret;
+ if (GDVIRTUAL_CALL(_get_length, ret)) {
+ return ret;
+ }
+ return 0;
+}
+
+double VideoStreamPlayback::get_playback_position() const {
+ double ret;
+ if (GDVIRTUAL_CALL(_get_playback_position, ret)) {
+ return ret;
+ }
+ return 0;
+}
+
+void VideoStreamPlayback::seek(double p_time) {
+ GDVIRTUAL_CALL(_seek, p_time);
+}
+
+void VideoStreamPlayback::set_audio_track(int p_idx) {
+ GDVIRTUAL_CALL(_set_audio_track, p_idx);
+}
+
+Ref VideoStreamPlayback::get_texture() const {
+ Ref ret;
+ if (GDVIRTUAL_CALL(_get_texture, ret)) {
+ return ret;
+ }
+ return nullptr;
+}
+
+void VideoStreamPlayback::update(double p_delta) {
+ if (!GDVIRTUAL_CALL(_update, p_delta)) {
+ ERR_FAIL_MSG("VideoStreamPlayback::update unimplemented");
+ }
+}
+
+void VideoStreamPlayback::set_mix_callback(AudioMixCallback p_callback, void *p_userdata) {
+ mix_callback = p_callback;
+ mix_udata = p_userdata;
+}
+
+int VideoStreamPlayback::get_channels() const {
+ int ret;
+ if (GDVIRTUAL_CALL(_get_channels, ret)) {
+ _channel_count = ret;
+ return ret;
+ }
+ return 0;
+}
+
+int VideoStreamPlayback::get_mix_rate() const {
+ int ret;
+ if (GDVIRTUAL_CALL(_get_mix_rate, ret)) {
+ return ret;
+ }
+ return 0;
+}
+
+int VideoStreamPlayback::mix_audio(int num_frames, PackedFloat32Array buffer, int offset) {
+ if (num_frames <= 0) {
+ return 0;
+ }
+ if (!mix_callback) {
+ return -1;
+ }
+ ERR_FAIL_INDEX_V(offset, buffer.size(), -1);
+ ERR_FAIL_INDEX_V((_channel_count < 1 ? 1 : _channel_count) * num_frames - 1, buffer.size() - offset, -1);
+ return mix_callback(mix_udata, buffer.ptr() + offset, num_frames);
+}
+
+/* --- NOTE VideoStream starts here. ----- */
+
+Ref VideoStream::instantiate_playback() {
+ Ref ret;
+ if (GDVIRTUAL_CALL(_instantiate_playback, ret)) {
+ ERR_FAIL_COND_V_MSG(ret.is_null(), nullptr, "Plugin returned null playback");
+ ret->set_audio_track(audio_track);
+ return ret;
+ }
+ return nullptr;
+}
+
+void VideoStream::set_file(const String &p_file) {
+ file = p_file;
+}
+
+String VideoStream::get_file() {
+ return file;
+}
+
+void VideoStream::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_file", "file"), &VideoStream::set_file);
+ ClassDB::bind_method(D_METHOD("get_file"), &VideoStream::get_file);
+
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "file"), "set_file", "get_file");
+
+ GDVIRTUAL_BIND(_instantiate_playback);
+}
+
+VideoStream::VideoStream() {
+}
+
+VideoStream::~VideoStream() {
+}
+
+void VideoStream::set_audio_track(int p_track) {
+ audio_track = p_track;
+}
diff --git a/scene/resources/video_stream.h b/scene/resources/video_stream.h
index f83c621d0a6d..b91a7acf350a 100644
--- a/scene/resources/video_stream.h
+++ b/scene/resources/video_stream.h
@@ -31,6 +31,7 @@
#ifndef VIDEO_STREAM_H
#define VIDEO_STREAM_H
+#include "core/io/file_access.h"
#include "scene/resources/texture.h"
class VideoStreamPlayback : public Resource {
@@ -39,40 +40,77 @@ class VideoStreamPlayback : public Resource {
public:
typedef int (*AudioMixCallback)(void *p_udata, const float *p_data, int p_frames);
- virtual void stop() = 0;
- virtual void play() = 0;
+protected:
+ AudioMixCallback mix_callback = nullptr;
+ void *mix_udata = nullptr;
+ mutable int _channel_count = 0; // Used only to assist with bounds checking in mix_audio.
- virtual bool is_playing() const = 0;
+ static void _bind_methods();
+ GDVIRTUAL0(_stop);
+ GDVIRTUAL0(_play);
+ GDVIRTUAL0RC(bool, _is_playing);
+ GDVIRTUAL1(_set_paused, bool);
+ GDVIRTUAL0RC(bool, _is_paused);
+ GDVIRTUAL0RC(double, _get_length);
+ GDVIRTUAL0RC(double, _get_playback_position);
+ GDVIRTUAL1(_seek, double);
+ GDVIRTUAL1(_set_audio_track, int);
+ GDVIRTUAL0RC(Ref, _get_texture);
+ GDVIRTUAL1(_update, double);
+ GDVIRTUAL0RC(int, _get_channels);
+ GDVIRTUAL0RC(int, _get_mix_rate);
- virtual void set_paused(bool p_paused) = 0;
- virtual bool is_paused() const = 0;
+ int mix_audio(int num_frames, PackedFloat32Array buffer = {}, int offset = 0);
- virtual void set_loop(bool p_enable) = 0;
- virtual bool has_loop() const = 0;
+public:
+ VideoStreamPlayback();
+ virtual ~VideoStreamPlayback();
- virtual double get_length() const = 0;
+ virtual void stop();
+ virtual void play();
- virtual double get_playback_position() const = 0;
- virtual void seek(double p_time) = 0;
+ virtual bool is_playing() const;
- virtual void set_audio_track(int p_idx) = 0;
+ virtual void set_paused(bool p_paused);
+ virtual bool is_paused() const;
- virtual Ref get_texture() const = 0;
+ virtual double get_length() const;
- virtual void update(double p_delta) = 0;
+ virtual double get_playback_position() const;
+ virtual void seek(double p_time);
- virtual void set_mix_callback(AudioMixCallback p_callback, void *p_userdata) = 0;
- virtual int get_channels() const = 0;
- virtual int get_mix_rate() const = 0;
+ virtual void set_audio_track(int p_idx);
+
+ virtual Ref get_texture() const;
+ virtual void update(double p_delta);
+
+ virtual void set_mix_callback(AudioMixCallback p_callback, void *p_userdata);
+ virtual int get_channels() const;
+ virtual int get_mix_rate() const;
};
class VideoStream : public Resource {
GDCLASS(VideoStream, Resource);
- OBJ_SAVE_TYPE(VideoStream); // Saves derived classes with common type so they can be interchanged.
+ OBJ_SAVE_TYPE(VideoStream);
+
+protected:
+ static void
+ _bind_methods();
+
+ GDVIRTUAL0R(Ref, _instantiate_playback);
+
+ String file;
+ int audio_track = 0;
public:
- virtual void set_audio_track(int p_track) = 0;
- virtual Ref instantiate_playback() = 0;
+ void set_file(const String &p_file);
+ String get_file();
+
+ virtual void set_audio_track(int p_track);
+ virtual Ref instantiate_playback();
+
+ VideoStream();
+ ~VideoStream();
};
#endif // VIDEO_STREAM_H