reckfellENGINE

Видео: запуск, пауза и перемотка

Полный интерфейс C#, Theora OGV, сохранение пропорций, цикл и хромакей.

На этой странице

Поддерживаемый файл

Встроенный декодер этой поставки читает Theora в контейнере Ogg, расширение .ogv. Расширение не перекодирует содержимое: переименование MP4 в OGV не поможет. Другие форматы требуют отдельной реализации VideoStream/расширения; встроенную поддержку MP4, H.264 или WebM здесь не обещает ни код, ни пример.

Скачать учебный OGV для Reckfell 1.0.0. Это собственный ролик из примера записи кадров: 512 × 288, 30 кадров/с, 4 с. Для обучения не нужны внешние видеоматериалы или установка кодировщика.

Запустить интерфейс

Создайте уровень с корнем Control. Сохраните ролик в Clips/recording-slate.ogv, добавьте VideoDemo.cs, прикрепите к корню, соберите C#-проект и запустите F6. Нажмите Запустить. Синий квадрат движется по экрану; Пауза сохраняет позицию, Продолжить возобновляет воспроизведение, Остановить прекращает его. Следующее нажатие Запустить начинает ролик сначала.

VideoDemo.cs
C#
using System;
using Reckfell;

public partial class VideoDemo : Control
{
    [Export(PropertyHint.File,"*.ogv")] public string VideoPath { get; set; } = "asset://Clips/recording-slate.ogv";
    public VideoStreamPlayer Video { get; private set; } = null!;
    public Label Status { get; private set; } = null!;
    public Button PauseButton { get; private set; } = null!;
    public int FinishedCount { get; private set; }
    private bool _russian;
    private string Text(string ru,string en) => _russian ? ru : en;

    public override void _Ready()
    {
        _russian=TranslationServer.GetLocale().StartsWith("ru",StringComparison.OrdinalIgnoreCase);
        SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);
        var layout=new VBoxContainer { Name="Layout" }; AddChild(layout); layout.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);
        var aspect=new AspectRatioContainer
        {
            Name="Aspect",Ratio=16f/9,StretchMode=AspectRatioContainer.StretchModeEnum.Fit,
            SizeFlagsVertical=SizeFlags.ExpandFill
        };
        Video=new VideoStreamPlayer { Name="Video",Expand=true,Autoplay=false,Loop=false };
        layout.AddChild(aspect); aspect.AddChild(Video);
        var buttons=new HBoxContainer { Name="Buttons" }; layout.AddChild(buttons);
        var play=new Button { Name="PlayButton",Text=Text("Запустить","Play") };
        PauseButton=new Button { Name="PauseButton",Text=Text("Пауза","Pause") };
        var stop=new Button { Name="StopButton",Text=Text("Остановить","Stop") };
        buttons.AddChild(play); buttons.AddChild(PauseButton); buttons.AddChild(stop);
        Status=new Label { Name="Status",Text=Text("Готово к запуску","Ready") }; layout.AddChild(Status);
        play.Pressed+=() => ShowResult(LoadAndPlay());
        PauseButton.Pressed+=TogglePause;
        stop.Pressed+=StopVideo;
        Video.Finished+=() =>
        {
            FinishedCount++;
            Status.Text=Text("Воспроизведение завершено","Finished");
            PauseButton.Text=Text("Пауза","Pause");
        };
    }

    public Error LoadAndPlay()
    {
        if (!IsInsideTree()) return Error.Unconfigured;
        if (!FileAccess.FileExists(VideoPath)) return Error.FileNotFound;
        if (!VideoPath.EndsWith(".ogv",StringComparison.OrdinalIgnoreCase)) return Error.Unavailable;
        var stream=ResourceLoader.Load<VideoStream>(VideoPath);
        if (stream is null) return Error.FileUnrecognized;
        Video.Paused=false; Video.Stream=stream; Video.Play();
        PauseButton.Text=Text("Пауза","Pause");
        Status.Text=Text("Воспроизведение","Playing");
        return Error.Ok;
    }

    private void ShowResult(Error error)
    {
        if (error != Error.Ok) Status.Text=$"{Text("Ошибка","Error")}: {error}";
    }

    public void TogglePause()
    {
        if (!Video.IsPlaying()) return;
        Video.Paused=!Video.Paused;
        PauseButton.Text=Video.Paused ? Text("Продолжить","Resume") : Text("Пауза","Pause");
        Status.Text=Video.Paused ? Text("Пауза","Paused") : Text("Воспроизведение","Playing");
    }

    public void StopVideo()
    {
        Video.Stop(); Video.Paused=false;
        PauseButton.Text=Text("Пауза","Pause"); Status.Text=Text("Остановлено","Stopped");
    }

    public Error SeekTo(double seconds)
    {
        double length=Video.GetStreamLength();
        if (!double.IsFinite(seconds) || seconds<0 || length<=0 || seconds>=length) return Error.InvalidParameter;
        Video.StreamPosition=seconds;
        return Error.Ok;
    }
}

UI выбирает RU или EN при создании по TranslationServer.GetLocale(). LoadAndPlay() проверяет путь до замены текущего потока; отсутствующий файл не останавливает уже играющий ролик. На первом запуске без файла статус покажет ошибку вместо обращения к null.

Размер и пропорции

Expand = true позволяет контейнеру управлять размером VideoStreamPlayer вместо минимального размера кадра. AspectRatioContainer с Ratio = 16/9 и Fit сохраняет пропорции. Для ролика с другими пропорциями задайте соответствующий Ratio; чёрные поля при несовпадении сторон — нормальны. Растягивание самого проигрывателя на произвольный прямоугольник не гарантирует сохранение пропорций.

Время и завершение

ОперацияКонтракт
Paused = trueПриостанавливает время; IsPlaying() при этом может оставаться true
Paused = falseПродолжает с сохранённой позиции
Stop()Останавливает; это не естественное окончание и не сигнал Finished
StreamPosition = secondsУ текущего Theora-декодера есть перемотка; задавайте допустимое время внутри длины
Loop = trueПовторяет ролик, не выдавая Finished на каждом обороте

SeekTo() принимает секунды и отвергает отрицательные значения, NaN и позицию на/за концом. Декодирование может пройти от ключевого кадра, поэтому перемотка длинного файла не обязана быть мгновенной. Новый видимый кадр проверяйте после обработки декодера; присваивание времени не является сигналом завершения. Нельзя автоматически переносить поведение Theora на произвольный плагин VideoStream.

Цикл не является обещанием бесшовного звука и отсутствия паузы на стыке. Для игрового состояния и отмены храните отдельный флаг, а не вечное ожидание Finished у зацикленного ролика.

Звук и нагрузка

Проверьте AudioTrack, Bus, VolumeDb и слышимость шины. В учебном ролике звуковая дорожка тихая намеренно. Theora декодируется на CPU: измеряйте разрешение, FPS и число одновременных потоков на целевом устройстве. BufferingMsec не включает аппаратное декодирование и не исправляет отсутствующий кодек. Внешний сетевой URL не становится HTTP-стримом от одного присваивания VideoPath; пример читает локальные ресурсы.

Видео на поверхности 3D

Получите живую текстуру через GetVideoTexture() и назначьте её StandardMaterial3D.AlbedoTexture; материалу можно выбрать Unshaded, если освещение не должно менять видео. Сам VideoStreamPlayer оставьте в дереве и запустите. Не копируйте каждый кадр через GetImage() для обычного показа — это ненужное чтение с GPU. Если нужна целая интерфейсная композиция, отрисуйте её в SubViewport и используйте его текстуру.

Хромакей

Сохраните код как ChromaKey.reckfellshader, создайте ShaderMaterial и назначьте его свойству Material проигрывателя. Цвет по умолчанию зелёный; threshold и положительная softness управляют удалением фона и мягкостью границы. Шейдер сохраняет Modulate через цвет вершин.

Шейдер
shader_type canvas_item;

uniform vec4 key_color : source_color = vec4(0.0, 1.0, 0.0, 1.0);
uniform float threshold : hint_range(0.0, 1.0) = 0.15;
uniform float softness : hint_range(0.001, 0.3) = 0.03;
varying vec4 tint;

void vertex() {
    tint = COLOR;
}

void fragment() {
    vec4 pixel = texture(TEXTURE, UV);
    float difference = distance(pixel.rgb, key_color.rgb);
    float alpha = smoothstep(threshold - softness, threshold + softness, difference);
    COLOR = pixel * tint;
    COLOR.a *= alpha;
}

Theora-файл не получает альфа-канал от этого шейдера: прозрачность вычисляется при показе. Сжатие смешивает цвета у границ, поэтому чистый фон исходника и умеренный порог важнее попытки исправить всё одним значением.

Диагностика

СимптомПроверка
FileNotFoundИмя, регистр, папка Clips и включение ресурса в экспорт
FileUnrecognizedНастоящий кодек Theora, корректный Ogg и доступный декодер
Кнопка Запустить ничего не показываетЛог ошибок, размер Control и валидность Stream
Видео растянутоExpand, Ratio и StretchMode контейнера
Позиция не меняетсяPaused, скорость, пауза дерева и обработка кадров
Finished не приходитLoop или явная остановка вместо окончания
MP4 работает в браузере, но не здесьБраузерный декодер не добавляет формат в VideoStreamPlayer
Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

Вход в аккаунт

Войдите в аккаунт Reckfell.

Один аккаунт для всей платформы Reckfell.