' MediaCore Digital Signage - BrightSign Autorun ' For BrightSignOS 8.x (XT/XD/HD Series) ' Server: https://mediacore.fixittech.us ' Generated: 2026-02-03T05:22:13.640Z Sub Main() print "============================================" print "MediaCore Digital Signage" print "BrightSign Native Player v1.0" print "============================================" g = GetGlobalAA() g.msgPort = CreateObject("roMessagePort") g.serverUrl = "https://mediacore.fixittech.us" di = CreateObject("roDeviceInfo") g.serialNumber = di.GetDeviceUniqueId() g.model = di.GetModel() g.firmware = di.GetVersion() g.family = di.GetFamily() print "Serial: "; g.serialNumber print "Model: "; g.model print "Firmware: "; g.firmware ' Log available audio outputs (safe, read-only) LogAudioOutputs() SetupVideoMode() SetupNetwork() CreateDirectory("content") EnableZoneSupport() InitializeVideoPlayer() InitializeHtmlWidget() InitializeUdpCommunication() MainEventLoop() End Sub Sub SetupVideoMode() print "[Video] Setting up video mode..." g = GetGlobalAA() vm = CreateObject("roVideoMode") g.screenWidth = vm.GetResX() g.screenHeight = vm.GetResY() if g.screenWidth >= 3840 then print "[Video] 4K mode active" g.is4K = true else print "[Video] HD mode active" g.is4K = false end if vm = invalid End Sub Sub SetupNetwork() print "[Network] Setting up network..." g = GetGlobalAA() g.ipAddress = "" gotIp = false nc = CreateObject("roNetworkConfiguration", 0) if nc <> invalid then currentConfig = nc.GetCurrentConfig() if currentConfig <> invalid and currentConfig.ip4_address <> invalid and currentConfig.ip4_address <> "" then g.ipAddress = currentConfig.ip4_address print "[Network] Wired IP: "; g.ipAddress gotIp = true end if end if if not gotIp then nc = CreateObject("roNetworkConfiguration", 1) if nc <> invalid then currentConfig = nc.GetCurrentConfig() if currentConfig <> invalid and currentConfig.ip4_address <> invalid and currentConfig.ip4_address <> "" then g.ipAddress = currentConfig.ip4_address print "[Network] Wireless IP: "; g.ipAddress gotIp = true end if end if end if if not gotIp then print "[Network] Waiting for network..." retries = 0 while g.ipAddress = "" and retries < 60 sleep(1000) retries = retries + 1 nc = CreateObject("roNetworkConfiguration", 0) if nc <> invalid then currentConfig = nc.GetCurrentConfig() if currentConfig <> invalid and currentConfig.ip4_address <> invalid and currentConfig.ip4_address <> "" then g.ipAddress = currentConfig.ip4_address print "[Network] Got IP: "; g.ipAddress end if end if end while end if End Sub Sub EnableZoneSupport() print "[Zones] Enabling zone support..." vm = CreateObject("roVideoMode") vm.SetGraphicsZOrder("front") vm = invalid End Sub Sub InitializeVideoPlayer() print "[Video] Initializing native video player..." g = GetGlobalAA() g.videoRect = CreateObject("roRectangle", 0, 0, g.screenWidth, g.screenHeight) g.videoPlayer = CreateObject("roVideoPlayer") if g.videoPlayer <> invalid then g.videoPlayer.SetRectangle(g.videoRect) g.videoPlayer.SetPort(g.msgPort) g.videoPlayer.SetLoopMode(0) g.videoPlayer.SetViewMode(0) g.videoPlayer.SetAudioOutput(0) g.videoPlayer.SetVolume(100) print "[Video] Native video player ready" end if g.imagePlayer = CreateObject("roImagePlayer") if g.imagePlayer <> invalid then g.imagePlayer.SetDefaultMode(1) g.imagePlayer.SetRectangle(g.videoRect) print "[Image] Native image player ready" end if End Sub Sub InitializeHtmlWidget() print "[HTML] Initializing HTML widget with Node.js..." g = GetGlobalAA() config = CreateObject("roAssociativeArray") config.nodejs_enabled = true config.brightsign_js_objects_enabled = true config.javascript_enabled = true config.mouse_enabled = false config.storage_path = "SD:/content" config.storage_quota = 10737418240 config.port = g.msgPort ' Load index.html from server for automatic updates across all devices ' Falls back to local file if server URL not configured config.url = g.serverUrl + "/brightsign/index.html" config.hwz_default = "on" security = CreateObject("roAssociativeArray") security.websecurity = false security.insecure_https_enabled = true config.security_params = security ' Make HTML widget full screen for branded waiting screen htmlRect = CreateObject("roRectangle", 0, 0, g.screenWidth, g.screenHeight) g.htmlWidget = CreateObject("roHtmlWidget", htmlRect, config) if g.htmlWidget <> invalid then print "[HTML] HTML widget with Node.js created" g.htmlWidget.Show() end if End Sub Sub InitializeUdpCommunication() print "[UDP] Setting up UDP communication..." g = GetGlobalAA() g.udpReceiver = CreateObject("roDatagramReceiver", 5000) if g.udpReceiver <> invalid then g.udpReceiver.SetPort(g.msgPort) print "[UDP] Listening on port 5000" end if g.udpSender = CreateObject("roDatagramSender") if g.udpSender <> invalid then g.udpSender.SetDestination("127.0.0.1", 5001) print "[UDP] Sender configured for port 5001" end if g.currentPlaylist = CreateObject("roArray", 0, true) g.currentIndex = 0 g.isPlaying = false g.layoutMode = false g.zonePlayers = invalid ' IPTV state g.iptvMode = false g.currentIptvChannel = invalid End Sub Sub MainEventLoop() print "[Main] Starting event loop..." g = GetGlobalAA() ' HTML widget shows branded waiting screen, no need for text widget initially SendToNodeJs("ready", g.serialNumber) g.contentTimer = CreateObject("roTimer") g.contentTimer.SetPort(g.msgPort) contentTimerId = g.contentTimer.GetIdentity() while true msg = wait(0, g.msgPort) msgType = type(msg) if msgType = "roVideoEvent" then ' Check if we're in layout mode if g.layoutMode = true then HandleZoneVideoEvent(msg) else HandleVideoEvent(msg) end if else if msgType = "roDatagramEvent" then HandleUdpCommand(msg) else if msgType = "roTimerEvent" then ' Check if this is a zone timer if g.layoutMode = true then HandleZoneTimerEvent(msg) else if msg.GetSourceIdentity() = contentTimerId then PlayNextContent() end if else if msgType = "roHtmlWidgetEvent" then HandleHtmlEvent(msg) end if end while End Sub Sub HandleVideoEvent(event As Object) g = GetGlobalAA() eventCode = event.GetInt() ' BrightSign roVideoEvent codes: ' 1 = MediaStarted (loading) ' 3 = MediaPlaying (SUCCESS - now playing) ' 8 = MediaEnded (finished) ' 4 = MediaError (actual error) ' Handle IPTV events separately if g.iptvMode then if eventCode = 3 then print "[IPTV] Stream now playing" channelStr = "" if g.currentIptvChannel <> invalid then channelStr = g.currentIptvChannel SendToNodeJs("iptv_playing", channelStr) else if eventCode = 4 then print "[IPTV] Stream error" SendToNodeJs("iptv_error", "Stream playback error") else if eventCode = 1 then print "[IPTV] Stream loading..." end if return end if ' Normal signage video events if eventCode = 8 then print "[Video] Playback ended" SendToNodeJs("playback_complete", Str(g.currentIndex)) PlayNextContent() else if eventCode = 3 then print "[Video] Now playing" g.isPlaying = true SendToNodeJs("playback_started", Str(g.currentIndex)) else if eventCode = 1 then print "[Video] Media started loading" else if eventCode = 4 then print "[Video] Playback error" SendToNodeJs("playback_error", Str(g.currentIndex)) PlayNextContent() end if End Sub Sub HandleUdpCommand(event As Object) g = GetGlobalAA() data = event.GetString() print "[UDP] Received: "; data json = ParseJson(data) if json <> invalid then command = "" if json.command <> invalid then command = json.command if command = "play_video" then if json.path <> invalid then PlayVideo(json.path) else if command = "play_image" then duration = 10 if json.duration <> invalid then duration = json.duration if json.path <> invalid then PlayImage(json.path, duration) else if command = "set_playlist" then if json.items <> invalid then StopLayoutPlayback() g.currentPlaylist = json.items g.currentIndex = 0 g.layoutMode = false print "[Playlist] Set "; g.currentPlaylist.Count(); " items" if g.currentPlaylist.Count() > 0 then PlayCurrentContent() end if else if command = "set_layout" then if json.layout <> invalid then SetupLayout(json.layout) end if else if command = "stop" then StopPlayback() else if command = "next" then PlayNextContent() else if command = "previous" then PlayPreviousContent() else if command = "status" then SendStatus() else if command = "reboot" then RebootSystem() else if command = "play_iptv" then if json.stream_url <> invalid then channelName = "" if json.channel_name <> invalid then channelName = json.channel_name PlayIptvStream(json.stream_url, channelName) end if else if command = "stop_iptv" then StopIptvPlayback() else if command = "set_mode" then if json.mode <> invalid then g.iptvMode = (json.mode = "iptv") if not g.iptvMode then StopIptvPlayback() end if end if end if end if End Sub Sub HandleHtmlEvent(event As Object) eventData = event.GetData() if type(eventData) = "roAssociativeArray" then if eventData.reason <> invalid then print "[HTML] Event: "; eventData.reason end if end if End Sub Sub PlayVideo(filePath As String) print "[Video] Playing: "; filePath g = GetGlobalAA() if g.videoPlayer <> invalid then g.videoPlayer.Stop() g.contentTimer.Stop() ' Hide HTML widget so video is visible if g.htmlWidget <> invalid then g.htmlWidget.Hide() aa = CreateObject("roAssociativeArray") aa.Filename = filePath ok = g.videoPlayer.PlayFile(aa) if ok then g.isPlaying = true print "[Video] Started: "; filePath else print "[Video] ERROR: Failed to play: "; filePath SendToNodeJs("playback_error", filePath) end if end if End Sub Sub PlayImage(filePath As String, duration As Integer) print "[Image] Displaying: "; filePath g = GetGlobalAA() if g.imagePlayer <> invalid then if g.videoPlayer <> invalid then g.videoPlayer.Stop() g.contentTimer.Stop() ' Hide HTML widget so image is visible if g.htmlWidget <> invalid then g.htmlWidget.Hide() ok = g.imagePlayer.DisplayFile(filePath) if ok then g.isPlaying = true g.imagePlayer.Show() if duration > 0 then g.contentTimer.SetElapsed(duration, 0) g.contentTimer.Start() end if SendToNodeJs("playback_started", filePath) else SendToNodeJs("playback_error", filePath) end if end if End Sub Sub PlayCurrentContent() g = GetGlobalAA() if g.currentPlaylist.Count() = 0 then ShowWaitingScreen() else if g.currentIndex >= g.currentPlaylist.Count() then g.currentIndex = 0 item = g.currentPlaylist[g.currentIndex] itemPath = "" itemType = "video" itemDuration = 10 if item.path <> invalid then itemPath = item.path if item.type <> invalid then itemType = item.type if item.duration <> invalid then itemDuration = item.duration print "[Playlist] Playing item "; Str(g.currentIndex + 1); "/"; Str(g.currentPlaylist.Count()) ' Notify Node.js of item change for real-time diagnostics SendItemChange(g.currentIndex) if itemType = "video" then PlayVideo(itemPath) else PlayImage(itemPath, itemDuration) end if end if End Sub Sub PlayNextContent() g = GetGlobalAA() g.currentIndex = g.currentIndex + 1 if g.currentIndex >= g.currentPlaylist.Count() then g.currentIndex = 0 PlayCurrentContent() End Sub Sub PlayPreviousContent() g = GetGlobalAA() g.currentIndex = g.currentIndex - 1 if g.currentIndex < 0 then g.currentIndex = g.currentPlaylist.Count() - 1 if g.currentIndex < 0 then g.currentIndex = 0 PlayCurrentContent() End Sub Sub StopPlayback() g = GetGlobalAA() StopLayoutPlayback() if g.videoPlayer <> invalid then g.videoPlayer.Stop() g.contentTimer.Stop() g.isPlaying = false g.layoutMode = false ' Show HTML widget for waiting screen if g.htmlWidget <> invalid then g.htmlWidget.Show() End Sub Sub StopLayoutPlayback() g = GetGlobalAA() ' Stop all zone players if g.zonePlayers <> invalid then for each zonePlayer in g.zonePlayers if zonePlayer.videoPlayer <> invalid then zonePlayer.videoPlayer.Stop() end if if zonePlayer.timer <> invalid then zonePlayer.timer.Stop() end if end for g.zonePlayers = invalid end if End Sub Sub SetupLayout(layout As Object) print "[Layout] Setting up layout: "; layout.name g = GetGlobalAA() ' Stop any existing playback StopPlayback() g.layoutMode = true g.isPlaying = true ' Hide HTML widget to show video zones if g.htmlWidget <> invalid then g.htmlWidget.Hide() ' Initialize zone players array g.zonePlayers = CreateObject("roArray", 0, true) ' Get screen dimensions screenWidth = g.screenWidth screenHeight = g.screenHeight ' Process each zone zones = layout.zones if zones <> invalid then for i = 0 to zones.Count() - 1 zone = zones[i] CreateZonePlayer(zone, screenWidth, screenHeight, i) end for end if print "[Layout] Setup complete with "; g.zonePlayers.Count(); " zones" SendToNodeJs("layout_started", layout.name) End Sub Sub CreateZonePlayer(zone As Object, screenWidth As Integer, screenHeight As Integer, index As Integer) g = GetGlobalAA() print "[Zone] Creating zone: "; zone.name; " type: "; zone.content_type ' Calculate pixel coordinates from percentages x = Int(zone.x * screenWidth / 100) y = Int(zone.y * screenHeight / 100) w = Int(zone.width * screenWidth / 100) h = Int(zone.height * screenHeight / 100) print "[Zone] Position: "; x; ","; y; " Size: "; w; "x"; h ' Create zone player object zonePlayer = CreateObject("roAssociativeArray") zonePlayer.zone = zone zonePlayer.index = index zonePlayer.currentIndex = 0 zonePlayer.items = zone.items zonePlayer.rect = CreateObject("roRectangle", x, y, w, h) zonePlayer.isPlaying = false ' Create player based on content type contentType = zone.content_type if contentType = "video" or contentType = "image" or contentType = "playlist" then if zone.items <> invalid and zone.items.Count() > 0 then zonePlayer.videoPlayer = CreateObject("roVideoPlayer") if zonePlayer.videoPlayer <> invalid then zonePlayer.videoPlayer.SetRectangle(zonePlayer.rect) zonePlayer.videoPlayer.SetPort(g.msgPort) zonePlayer.videoPlayer.SetLoopMode(0) zonePlayer.videoPlayer.SetViewMode(0) zonePlayer.videoPlayer.SetVolume(100) if zone.muted = true then zonePlayer.videoPlayer.SetVolume(0) ' Create timer for image durations zonePlayer.timer = CreateObject("roTimer") zonePlayer.timer.SetPort(g.msgPort) ' Store player identity for event matching zonePlayer.playerId = zonePlayer.videoPlayer.GetIdentity() zonePlayer.timerId = zonePlayer.timer.GetIdentity() end if ' Create image player for images in this zone zonePlayer.imagePlayer = CreateObject("roImagePlayer") if zonePlayer.imagePlayer <> invalid then zonePlayer.imagePlayer.SetDefaultMode(1) zonePlayer.imagePlayer.SetRectangle(zonePlayer.rect) end if end if else if contentType = "clock" then ' Create HTML widget for clock display clockHtml = "data:text/html," + CreateClockHtml() config = CreateObject("roAssociativeArray") config.nodejs_enabled = false config.javascript_enabled = true config.mouse_enabled = false config.url = clockHtml zonePlayer.htmlWidget = CreateObject("roHtmlWidget", zonePlayer.rect, config) if zonePlayer.htmlWidget <> invalid then zonePlayer.htmlWidget.Show() print "[Zone] Clock widget created" end if else if contentType = "text" then ' Create text widget for text/ticker display textContent = "MediaCore" if zone.config <> invalid and zone.config.text <> invalid then textContent = zone.config.text end if twParams = CreateObject("roAssociativeArray") twParams.LineCount = 1 twParams.TextMode = 2 twParams.Rotation = 0 twParams.Alignment = 1 zonePlayer.textWidget = CreateObject("roTextWidget", zonePlayer.rect, 1, 2, twParams) if zonePlayer.textWidget <> invalid then zonePlayer.textWidget.PushString(textContent) zonePlayer.textWidget.Show() print "[Zone] Text widget created: "; textContent end if else if contentType = "weather" then ' Create HTML widget for weather display location = "auto" if zone.config <> invalid and zone.config.location <> invalid then location = zone.config.location end if weatherHtml = "data:text/html," + CreateWeatherHtml(location) config = CreateObject("roAssociativeArray") config.nodejs_enabled = false config.javascript_enabled = true config.mouse_enabled = false config.url = weatherHtml zonePlayer.htmlWidget = CreateObject("roHtmlWidget", zonePlayer.rect, config) if zonePlayer.htmlWidget <> invalid then zonePlayer.htmlWidget.Show() print "[Zone] Weather widget created for: "; location end if end if g.zonePlayers.Push(zonePlayer) ' Start playing content in this zone if zonePlayer.items <> invalid and zonePlayer.items.Count() > 0 then PlayZoneContent(index) end if End Sub Function CreateClockHtml() As String html = "" html = html + "
" html = html + "" return html End Function Function CreateWeatherHtml(location As String) As String html = "" html = html + "
" html = html + "
Loading weather...
" html = html + "
" html = html + "" return html End Function Sub PlayZoneContent(zoneIndex As Integer) g = GetGlobalAA() if g.zonePlayers = invalid or zoneIndex >= g.zonePlayers.Count() then return zonePlayer = g.zonePlayers[zoneIndex] if zonePlayer.items = invalid or zonePlayer.items.Count() = 0 then return item = zonePlayer.items[zonePlayer.currentIndex] print "[Zone "; zoneIndex; "] Playing: "; item.name; " type: "; item.type itemType = LCase(item.type) if itemType = "video" then if zonePlayer.videoPlayer <> invalid then zonePlayer.videoPlayer.Stop() zonePlayer.videoPlayer.PlayFile(item.path) zonePlayer.isPlaying = true end if else if itemType = "image" then if zonePlayer.imagePlayer <> invalid then zonePlayer.imagePlayer.DisplayFile(item.path) zonePlayer.isPlaying = true ' Set timer for image duration duration = item.duration if duration = invalid or duration = 0 then duration = 10 zonePlayer.timer.SetElapsed(duration, 0) zonePlayer.timer.Start() end if end if End Sub Sub PlayNextZoneContent(zoneIndex As Integer) g = GetGlobalAA() if g.zonePlayers = invalid or zoneIndex >= g.zonePlayers.Count() then return zonePlayer = g.zonePlayers[zoneIndex] if zonePlayer.items = invalid or zonePlayer.items.Count() = 0 then return ' Advance to next item zonePlayer.currentIndex = (zonePlayer.currentIndex + 1) mod zonePlayer.items.Count() g.zonePlayers[zoneIndex] = zonePlayer PlayZoneContent(zoneIndex) End Sub Sub HandleZoneVideoEvent(event As Object) g = GetGlobalAA() if g.zonePlayers = invalid then return eventCode = event.GetInt() sourceId = event.GetSourceIdentity() ' Find which zone this event belongs to for i = 0 to g.zonePlayers.Count() - 1 zonePlayer = g.zonePlayers[i] if zonePlayer.playerId <> invalid and zonePlayer.playerId = sourceId then if eventCode = 8 then ' Video ended - play next print "[Zone "; i; "] Video ended" PlayNextZoneContent(i) else if eventCode = 4 then ' Error - try next print "[Zone "; i; "] Video error" PlayNextZoneContent(i) end if exit for end if end for End Sub Sub HandleZoneTimerEvent(event As Object) g = GetGlobalAA() if g.zonePlayers = invalid then return sourceId = event.GetSourceIdentity() ' Find which zone timer fired for i = 0 to g.zonePlayers.Count() - 1 zonePlayer = g.zonePlayers[i] if zonePlayer.timerId <> invalid and zonePlayer.timerId = sourceId then print "[Zone "; i; "] Timer fired" PlayNextZoneContent(i) exit for end if end for End Sub Sub ShowWaitingScreen() print "[Display] Showing waiting screen..." g = GetGlobalAA() vm = CreateObject("roVideoMode") resX = vm.GetResX() resY = vm.GetResY() vm = invalid r = CreateObject("roRectangle", 0, resY/2 - 50, resX, 100) twParams = CreateObject("roAssociativeArray") twParams.LineCount = 2 twParams.TextMode = 2 twParams.Rotation = 0 twParams.Alignment = 1 tw = CreateObject("roTextWidget", r, 1, 2, twParams) tw.PushString("MediaCore Digital Signage") tw.PushString("Waiting for content...") tw.Show() g.waitingWidget = tw End Sub Sub SendToNodeJs(eventType As String, data As String) g = GetGlobalAA() if g.udpSender <> invalid then payload = CreateObject("roAssociativeArray") payload.event = eventType payload.data = data payload.serial = g.serialNumber dt = CreateObject("roDateTime") payload.timestamp = dt.ToISOString() jsonStr = FormatJson(payload) g.udpSender.Send(jsonStr) end if End Sub Sub SendItemChange(index As Integer) ' Send item_change event with index for real-time diagnostics g = GetGlobalAA() if g.udpSender <> invalid then payload = CreateObject("roAssociativeArray") payload.event = "item_change" payload.index = index payload.serial = g.serialNumber dt = CreateObject("roDateTime") payload.timestamp = dt.ToISOString() jsonStr = FormatJson(payload) g.udpSender.Send(jsonStr) end if End Sub ' === AUDIO CONFIGURATION FUNCTIONS === ' BrightSignOS 8.x Audio - SAFE VERSION (won't break video) Function CreateHdmiAudioOutput() As Object print "[AUDIO] Creating HDMI audio output..." hdmiAudio = CreateObject("roAudioOutput", "hdmi") if hdmiAudio = invalid then print "[AUDIO][WARN] roAudioOutput('hdmi') unavailable" return invalid end if print "[AUDIO] roAudioOutput('hdmi') OK" hdmiAudio.SetVolume(80) hdmiAudio.SetMute(false) return hdmiAudio End Function Sub BindAudioToVideoPlayer(videoPlayer As Object, hdmiAudio As Object) if videoPlayer = invalid then print "[AUDIO][WARN] videoPlayer invalid, skip audio" return end if if hdmiAudio = invalid then print "[AUDIO][WARN] hdmiAudio invalid, using default" videoPlayer.SetVolume(100) return end if audioOutputs = CreateObject("roArray", 1, false) audioOutputs.Push(hdmiAudio) videoPlayer.SetPcmAudioOutputs(audioOutputs) videoPlayer.SetVolume(100) print "[AUDIO] Audio bound to HDMI" End Sub Sub LogAudioOutputs() print "[AUDIO-DIAG] Available outputs:" validOutputs = ["hdmi", "analog", "spdif", "usb"] for each name in validOutputs ao = CreateObject("roAudioOutput", name) if ao <> invalid then print "[AUDIO-DIAG] "; name; " OK" next End Sub ' === IPTV BUFFER CONFIGURATION === ' These settings prioritize STABILITY over low latency ' Target: Zero audio dropouts for production IPTV (COM400/DirecTV headend style) Function GetIptvBufferConfig() As Object config = {} ' UDP socket receive buffer (bytes) - larger = more jitter tolerance ' Default OS is ~212KB, we want much larger for multicast stability config.udpBufferSize = 2097152 ' 2MB UDP socket buffer ' Pre-roll delay before starting playback (ms) ' Allows buffer to fill before decode begins config.preRollDelayMs = 500 ' 500ms pre-roll ' Audio delay (ms) - adds latency to audio output ' Helps audio stay in sync when video has to wait for packets config.audioDelayMs = 200 ' 200ms audio delay ' Video delay (ms) - adds latency to video output config.videoDelayMs = 200 ' 200ms video delay ' Enable low-latency mode? NO - we want stability config.lowLatencyMode = false return config End Function Sub LogBufferConfig(config As Object) print "[BUFFER] ==========================================" print "[BUFFER] IPTV Buffer Configuration (Stability Mode)" print "[BUFFER] UDP Socket Buffer: "; config.udpBufferSize; " bytes ("; config.udpBufferSize / 1048576; " MB)" print "[BUFFER] Pre-roll Delay: "; config.preRollDelayMs; " ms" print "[BUFFER] Audio Delay: "; config.audioDelayMs; " ms" print "[BUFFER] Video Delay: "; config.videoDelayMs; " ms" print "[BUFFER] Low Latency Mode: "; config.lowLatencyMode print "[BUFFER] ==========================================" End Sub Function AddBufferParamsToUrl(url As String, bufferSize As Integer) As String ' Add buffer_size parameter to UDP URL for larger socket buffer ' Format: udp://source@group:port?buffer_size=N if Instr(1, url, "?") > 0 then ' URL already has parameters, append with & return url + "&buffer_size=" + str(bufferSize).Trim() else ' No parameters yet, add with ? return url + "?buffer_size=" + str(bufferSize).Trim() end if End Function ' === IPTV PLAYBACK FUNCTIONS === Sub PlayIptvStream(streamUrl As String, channelName As String) print "[IPTV] ==========================================" print "[IPTV] Playing stream: "; streamUrl print "[IPTV] Channel: "; channelName print "[IPTV] Mode: STABILITY (max buffering, no dropouts)" g = GetGlobalAA() ' Detect stream type isMulticast = Left(streamUrl, 6) = "udp://" if isMulticast then print "[IPTV] Stream type: UDP Multicast (SSM)" else if Instr(1, streamUrl, ".m3u8") > 0 then print "[IPTV] Stream type: HLS" else print "[IPTV] Stream type: Other" end if ' Stop existing playback if g.videoPlayer <> invalid then g.videoPlayer.Stop() end if if g.iptvPlayer <> invalid then g.iptvPlayer.Stop() end if g.contentTimer.Stop() ' Hide HTML widget so video is visible if g.htmlWidget <> invalid then g.htmlWidget.Hide() ' === STEP 1: CREATE HDMI AUDIO OUTPUT === print "[IPTV] Step 1: Configure HDMI audio output" hdmiAudio = CreateHdmiAudioOutput() ' For UDP multicast, use roRtspStream passed to roVideoPlayer.PlayFile() if isMulticast then print "[IPTV] Using roRtspStream + roVideoPlayer for UDP multicast..." ' === STEP 2: GET BUFFER CONFIGURATION === print "[IPTV] Step 2: Configure buffering for stability" bufferConfig = GetIptvBufferConfig() LogBufferConfig(bufferConfig) ' === STEP 3: MODIFY URL WITH BUFFER PARAMS === bufferedUrl = AddBufferParamsToUrl(streamUrl, bufferConfig.udpBufferSize) print "[IPTV] Step 3: URL with buffer params: "; bufferedUrl ' === STEP 4: CREATE VIDEO PLAYER === print "[IPTV] Step 4: Create video player" iptvPlayer = CreateObject("roVideoPlayer") if iptvPlayer = invalid then print "[IPTV] ERROR: Failed to create roVideoPlayer" SendToNodeJs("iptv_error", "Failed to create roVideoPlayer") return end if ' Configure video player screenRect = CreateObject("roRectangle", 0, 0, g.screenWidth, g.screenHeight) iptvPlayer.SetRectangle(screenRect) iptvPlayer.SetPort(g.msgPort) iptvPlayer.SetVolume(100) ' === STEP 5: CONFIGURE AUDIO/VIDEO DELAYS FOR JITTER TOLERANCE === print "[IPTV] Step 5: Configure A/V delays for jitter tolerance" ' SetAudioDelay adds latency to audio output (helps with sync during jitter) iptvPlayer.SetAudioDelay(bufferConfig.audioDelayMs) print "[IPTV] SetAudioDelay("; bufferConfig.audioDelayMs; ")" ' SetVideoDelay adds latency to video output iptvPlayer.SetVideoDelay(bufferConfig.videoDelayMs) print "[IPTV] SetVideoDelay("; bufferConfig.videoDelayMs; ")" ' Note: EnableLowLatencyMode may not exist on all firmware versions ' Skipping this call to avoid runtime errors ' === STEP 6: BIND HDMI AUDIO === print "[IPTV] Step 6: Bind HDMI audio output" BindAudioToVideoPlayer(iptvPlayer, hdmiAudio) ' Store references g.iptvPlayer = iptvPlayer g.hdmiAudio = hdmiAudio ' === STEP 7: CREATE STREAM OBJECT === print "[IPTV] Step 7: Create roRtspStream with buffered URL" rtspStream = CreateObject("roRtspStream", bufferedUrl) if rtspStream = invalid then print "[IPTV] ERROR: Failed to create roRtspStream" print "[IPTV] URL was: "; bufferedUrl SendToNodeJs("iptv_error", "Failed to create roRtspStream") return end if print "[IPTV] roRtspStream created successfully" ' Store reference g.rtspStream = rtspStream ' === STEP 8: PRE-ROLL DELAY === ' Wait before starting playback to allow buffers to fill print "[IPTV] Step 8: Pre-roll delay ("; bufferConfig.preRollDelayMs; " ms)" print "[IPTV] Allowing network buffers to fill..." sleep(bufferConfig.preRollDelayMs) ' === STEP 9: START PLAYBACK === print "[IPTV] Step 9: Start playback" print "[IPTV] Calling roVideoPlayer.PlayFile({ rtsp: roRtspStream })" playParams = { rtsp: rtspStream } ok = iptvPlayer.PlayFile(playParams) print "[IPTV] PlayFile returned: "; ok if ok then g.iptvMode = true g.isPlaying = true g.currentIptvChannel = channelName print "[IPTV] ==========================================" print "[IPTV] UDP multicast started: "; channelName print "[IPTV] Buffer config applied for maximum stability" print "[IPTV] Audio routed to HDMI via SetPcmAudioOutputs()" print "[IPTV] ==========================================" SendToNodeJs("iptv_started", streamUrl) else print "[IPTV] ERROR: PlayFile failed for UDP multicast" print "[IPTV] URL: "; bufferedUrl SendToNodeJs("iptv_error", "PlayFile failed for UDP") end if else ' HLS and other formats: use roVideoPlayer directly print "[IPTV] Using roVideoPlayer for HLS/other..." iptvPlayer = CreateObject("roVideoPlayer") if iptvPlayer = invalid then print "[IPTV] ERROR: Failed to create roVideoPlayer" SendToNodeJs("iptv_error", "Failed to create video player") return end if ' Configure the player screenRect = CreateObject("roRectangle", 0, 0, g.screenWidth, g.screenHeight) iptvPlayer.SetRectangle(screenRect) iptvPlayer.SetPort(g.msgPort) iptvPlayer.SetLoopMode(true) iptvPlayer.SetVolume(100) ' === BIND HDMI AUDIO TO VIDEO PLAYER === BindAudioToVideoPlayer(iptvPlayer, hdmiAudio) ' Store references g.iptvPlayer = iptvPlayer g.hdmiAudio = hdmiAudio ' Play directly with URL print "[IPTV] Calling roVideoPlayer.PlayFile with: "; streamUrl ok = iptvPlayer.PlayFile(streamUrl) print "[IPTV] PlayFile returned: "; ok if ok then g.iptvMode = true g.isPlaying = true g.currentIptvChannel = channelName print "[IPTV] HLS started: "; channelName SendToNodeJs("iptv_started", streamUrl) else print "[IPTV] ERROR: PlayFile failed for "; streamUrl SendToNodeJs("iptv_error", "PlayFile failed for: " + streamUrl) end if end if print "[IPTV] ==========================================" End Sub Sub StopIptvPlayback() print "[IPTV] Stopping playback" g = GetGlobalAA() ' Stop video players (roRtspStream is controlled via roVideoPlayer, not directly) if g.videoPlayer <> invalid then g.videoPlayer.Stop() end if if g.iptvPlayer <> invalid then g.iptvPlayer.Stop() end if ' Clear rtspStream reference (no Stop() method on roRtspStream) g.rtspStream = invalid g.iptvMode = false g.isPlaying = false g.currentIptvChannel = invalid ' Show HTML widget (waiting screen) if g.htmlWidget <> invalid then g.htmlWidget.Show() SendToNodeJs("iptv_stopped", "") End Sub ' === END IPTV FUNCTIONS === Sub SendStatus() g = GetGlobalAA() status = CreateObject("roAssociativeArray") status.event = "status" status.serial = g.serialNumber status.model = g.model status.firmware = g.firmware status.ip = g.ipAddress status.isPlaying = g.isPlaying status.currentIndex = g.currentIndex status.playlistSize = g.currentPlaylist.Count() status.is4K = g.is4K status.screenWidth = g.screenWidth status.screenHeight = g.screenHeight status.iptvMode = g.iptvMode if g.currentIptvChannel <> invalid then status.iptvChannel = g.currentIptvChannel end if jsonStr = FormatJson(status) if g.udpSender <> invalid then g.udpSender.Send(jsonStr) End Sub