|
--!strict |
|
--!native |
|
-- module script |
|
|
|
local VideoPlayer = {} |
|
|
|
local ReplicatedStorage = game:GetService("ReplicatedStorage") |
|
local SoundService = game:GetService("SoundService") |
|
local RunService = game:GetService("RunService") |
|
local HttpService = game:GetService("HttpService") |
|
|
|
local Events = require(ReplicatedStorage.VideoPlayer.Events) |
|
|
|
|
|
|
|
type VideoInfo = { |
|
Framerate : number, |
|
Resolution : Vector2, |
|
AudioId : string, |
|
} |
|
|
|
local FRAMES_PER_BLOCK = 20*10 -- TODO: move to config, make it dependent on video framerate |
|
|
|
local pixels : {[string] : {[number] : buffer, Array : {number}}} = {} |
|
|
|
local function getVideoInfo(videoName : string) : VideoInfo |
|
local videoInfoString = Events.InitVideo:InvokeServer(videoName) |
|
local videoInfo = HttpService:JSONDecode(videoInfoString) |
|
videoInfo.Resolution = Vector2.new(videoInfo.Width, videoInfo.Height) |
|
videoInfo.Width = nil |
|
videoInfo.Height = nil |
|
return videoInfo |
|
end |
|
|
|
local function unloadPixels(videoName : string, blockIndex : number) |
|
pixels[videoName][blockIndex] = nil |
|
end |
|
|
|
local function downloadPixels(videoName : string, blockIndex : number, resolution : Vector2) |
|
-- TODO: get resolution by video name from cached video info |
|
pixels[videoName][blockIndex] = buffer.create(resolution.X * resolution.Y * 4 * FRAMES_PER_BLOCK) -- TODO: separate "isDownloading" flag |
|
Events.RequestGetFrames:FireServer(videoName, blockIndex) |
|
end |
|
|
|
Events.ResponseGetFrames.OnClientEvent:Connect(function(videoName : string, blockIndex : number, startByteIndex : number, framesBufferPart : buffer) |
|
local framesBuffer = pixels[videoName][blockIndex] |
|
buffer.copy(framesBuffer, startByteIndex, framesBufferPart) |
|
--buffer.writestring(framesBuffer, startByteIndex, framesStringPart) |
|
end) |
|
|
|
local function getPixels(videoName : string, frameIndex : number, resolution : Vector2) |
|
-- TODO: get resolution by video name from cached video info |
|
local blockIndex = frameIndex // FRAMES_PER_BLOCK |
|
local frameInBlockIndex = frameIndex % FRAMES_PER_BLOCK |
|
local framesBuffer = pixels[videoName][blockIndex] |
|
if framesBuffer == nil then |
|
warn(`No frames for block {blockIndex}`) |
|
return |
|
end |
|
debug.profilebegin("Creating pixels array") |
|
local frameSize = resolution.X * resolution.Y * 4 |
|
local startByteIndex = frameInBlockIndex * frameSize - 1 |
|
local endByteIndex = startByteIndex + frameSize - 1 |
|
for i = 1, frameSize do |
|
local byteIndex = startByteIndex + i |
|
local byte = buffer.readu8(framesBuffer, byteIndex) |
|
pixels[videoName].Array[i] = byte/255 |
|
end |
|
debug.profileend() |
|
end |
|
|
|
local function onHeartbeat(audio : Sound, videoInfo : VideoInfo, videoName : string, editableImage : EditableImage) |
|
local frameIndex = math.round(audio.TimePosition * videoInfo.Framerate) + 1 |
|
local blockIndex = frameIndex // FRAMES_PER_BLOCK |
|
|
|
debug.profilebegin("Unload/download pixels") |
|
unloadPixels(videoName, blockIndex-1) |
|
if not pixels[videoName][blockIndex+1] then -- TODO: separate "isDownloading" flag |
|
downloadPixels(videoName, blockIndex+1, videoInfo.Resolution) |
|
end |
|
debug.profileend() |
|
|
|
getPixels(videoName, frameIndex, videoInfo.Resolution) |
|
debug.profilebegin("Write pixels") |
|
editableImage:WritePixels( |
|
Vector2.zero, |
|
videoInfo.Resolution, |
|
pixels[videoName].Array |
|
) |
|
debug.profileend() |
|
end |
|
|
|
local function stopVideo(connection : RBXScriptConnection, videoName : string, editableImage : EditableImage, audio : Sound) |
|
connection:Disconnect() |
|
pixels[videoName] = nil |
|
editableImage:Destroy() |
|
audio:Destroy() |
|
end |
|
|
|
function VideoPlayer.Play(canvas : ImageLabel, videoName : string) |
|
|
|
local videoInfo = getVideoInfo(videoName) |
|
pixels[videoName] = { |
|
Array = table.create(videoInfo.Resolution.X * videoInfo.Resolution.Y * 4, 1) |
|
} |
|
downloadPixels(videoName, 0, videoInfo.Resolution) |
|
downloadPixels(videoName, 1, videoInfo.Resolution) |
|
task.wait(2) -- TODO: wait for initial pixels to download |
|
|
|
local editableImage = Instance.new("EditableImage") |
|
editableImage.Size = videoInfo.Resolution |
|
editableImage.Parent = canvas |
|
|
|
local audio = Instance.new("Sound") |
|
audio.Name = videoName |
|
audio.Volume = 0.15 |
|
audio.SoundId = videoInfo.AudioId |
|
audio.Parent = SoundService |
|
|
|
local connection = RunService.Heartbeat:Connect(function() |
|
onHeartbeat(audio, videoInfo, videoName, editableImage) |
|
end) |
|
|
|
audio.Ended:Connect(function() |
|
stopVideo(connection, videoName, editableImage, audio) |
|
end) |
|
audio:GetPropertyChangedSignal("Parent"):Connect(function() |
|
if not audio.Parent then |
|
stopVideo(connection, videoName, editableImage, audio) |
|
end |
|
end) |
|
audio:Play() |
|
|
|
end |
|
|
|
|
|
|
|
return VideoPlayer |