From 4c270b744f02b1e8809ec4465248d1d2ea7cdccb Mon Sep 17 00:00:00 2001 From: happy Date: Tue, 25 Aug 2026 16:25:42 -0400 Subject: [PATCH] Play feature under the menu; add mpv path; simplify VLC launch Root causes found this session (all player-side, disc is valid -- bd_info validates it and mpv plays the ISO's feature directly): - Plain 'vlc ' can't run ANY BD-J: system Java is 26, and libbluray's BD-J needs a JVM where System.setSecurityManager() works (<=21). run-vlc.sh pins mise Temurin 8, which is why it gets a menu at all. - The BD-J VFS-cache 'error creating /tmp/libbluray-bdj-cache' flood is libbluray's SecurityManager.canWrite() denying the cache root while an Xlet context is active. Fixed by pointing TMPDIR at LIBBLURAY_CACHE_ROOT (buda root), which canWrite() allows unconditionally. - Menu->playback failed with 'blurayReleaseVout: subpicture channel exists': VLC botches the background->playlist transition for an idle BD-J menu. Now the Xlet starts the feature immediately and draws the menu on top, so VLC stays in normal playing mode; MAIN FEATURE just hides the menu, any key brings it back, and the feature loops. Also: drop the redundant tools/vlc-plugins swap (system VLC already ships the bluray plugin), and add scripts/run-mpv.sh to play the feature with no BD-J/JVM at all. Vendored the SDK helloworld sample for reference. Co-Authored-By: Claude Opus 4.8 --- README.md | 21 +++-- scripts/run-mpv.sh | 29 +++++++ scripts/run-vlc.sh | 42 ++++++---- src/org/homebrew/AdjustmentsMenu.java | 66 ++++++++++++++- src/org/homebrew/MyXlet.java | 3 + vendor/bdj-sdk/samples/helloworld/.gitignore | 6 ++ vendor/bdj-sdk/samples/helloworld/Makefile | 80 ++++++++++++++++++ .../src/org/homebrew/LoggingUI.java | 82 +++++++++++++++++++ .../helloworld/src/org/homebrew/MyXlet.java | 46 +++++++++++ .../src/org/homebrew/bluray.MyXlet.perm | 12 +++ 10 files changed, 362 insertions(+), 25 deletions(-) create mode 100644 scripts/run-mpv.sh create mode 100644 vendor/bdj-sdk/samples/helloworld/.gitignore create mode 100644 vendor/bdj-sdk/samples/helloworld/Makefile create mode 100644 vendor/bdj-sdk/samples/helloworld/src/org/homebrew/LoggingUI.java create mode 100644 vendor/bdj-sdk/samples/helloworld/src/org/homebrew/MyXlet.java create mode 100644 vendor/bdj-sdk/samples/helloworld/src/org/homebrew/bluray.MyXlet.perm diff --git a/README.md b/README.md index f1c603f..6780e1d 100644 --- a/README.md +++ b/README.md @@ -30,20 +30,23 @@ build/adjustments.iso ``` `make run` previews the disc by pointing VLC's Blu-ray access module at the -`build/disc` BDMV **folder**, not the ISO: +`build/disc` BDMV **folder** (a real player reads the same tree from the ISO): ```sh vlc --bluray-menu bluray:///absolute/path/to/build/disc ``` -Why the folder and not the ISO: when libbluray opens a UDF `.iso`, its BD-J -runtime must copy `BDMV/JAR/00000.jar` out of the image into a temp cache dir -before the JVM can load it. On this setup that write is refused by libbluray's -BD-J `SecurityManager`, so the Xlet class is never found and VLC silently falls -back to playing the raw playlist with no menu. Reading the folder lets -libbluray load the JAR directly, so the menu Xlet runs. The ISO is still the -artifact you burn/distribute; real players read the JAR from the disc -filesystem directly and are unaffected. +BD-J cache note: to load the menu Xlet, libbluray copies the disc's JAR and +fonts into a VFS cache under `java.io.tmpdir` (default +`/tmp/libbluray-bdj-cache`). Its BD-J `SecurityManager.canWrite()` allows writes +under the buda/persistent roots unconditionally, but only allows the VFS cache +root when no Xlet context is active on the calling thread -- and that extraction +frequently runs while a context *is* active, so the write is denied and you see +a flood of `error creating .../libbluray-bdj-cache` followed by the Xlet failing +to load (no menu). `scripts/run-vlc.sh` fixes this by pointing `TMPDIR` at the +same directory it gives libbluray as `LIBBLURAY_CACHE_ROOT` (the buda root), so +the cache path is always writable. This is a libbluray/VLC runtime quirk, not a +defect in the authored disc. The initial menu is a Java Xlet overlay with a few bonus-feature placeholders and a hidden menu stub. The actual generated video is a short ffmpeg SMPTE bars diff --git a/scripts/run-mpv.sh b/scripts/run-mpv.sh new file mode 100644 index 0000000..53c66ba --- /dev/null +++ b/scripts/run-mpv.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Play the disc's feature straight from the ISO (or a BDMV folder) with mpv. +# +# mpv links libbluray and plays Blu-ray *titles/playlists* directly. It does NOT +# run BD-J -- there is no menu, no Xlet, no Java involved. This is purely "open +# the disc and watch the movie", and unlike the VLC/BD-J path it Just Works with +# no JVM or cache setup. Use scripts/run-vlc.sh if you want the BD-J menu. +# +# Pass a path to override the default (an .iso image or a BDMV folder). + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TARGET="${1:-$ROOT_DIR/build/adjustments.iso}" + +if [[ ! -e "$TARGET" ]]; then + echo "Missing disc target: $TARGET" >&2 + echo "Run: make" >&2 + exit 1 +fi + +if [[ -d "$TARGET" ]]; then + DEVICE="$(cd "$TARGET" && pwd)" +else + DEVICE="$(cd "$(dirname "$TARGET")" && pwd)/$(basename "$TARGET")" +fi + +# bd://longest plays the main title; --bluray-device points at the image/folder. +exec mpv --bluray-device="$DEVICE" "bd://longest" diff --git a/scripts/run-vlc.sh b/scripts/run-vlc.sh index e6f79e7..fb0ee2f 100755 --- a/scripts/run-vlc.sh +++ b/scripts/run-vlc.sh @@ -4,15 +4,9 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" MISE_BIN="${MISE_BIN:-$HOME/.local/bin/mise}" -# Default to the on-disk BDMV folder, not the ISO. -# -# When libbluray opens a UDF *image* (.iso), its BD-J stack must first copy -# BDMV/JAR/00000.jar out of the image into a temp cache dir. On this setup that -# write is refused by libbluray's BD-J SecurityManager (usingUdf=true, no disc -# root), so the JAR never mounts, the Xlet class is never found, and VLC falls -# back to playing the raw playlist with no menu. Opening the *folder* lets -# libbluray read the JAR directly, so the Xlet (menu) loads. Pass an explicit -# path to override (folder or .iso). +# Default to the on-disk BDMV folder; pass an explicit path to override (a +# folder or an .iso). The folder is just the simpler preview target -- a real +# player reads the JAR from the ISO's disc filesystem directly. TARGET="${1:-$ROOT_DIR/build/disc}" if [[ -d "$TARGET" && ! -d "$TARGET/BDMV" ]]; then @@ -43,14 +37,34 @@ if [[ -n "$JDK8_HOME" ]]; then export LD_LIBRARY_PATH="$JDK8_HOME/jre/lib/amd64/server:${LD_LIBRARY_PATH:-}" fi -LOCAL_VLC_PLUGINS="$ROOT_DIR/tools/vlc-plugins/usr/lib/vlc/plugins" -if [[ -d "$LOCAL_VLC_PLUGINS" ]]; then - export VLC_PLUGIN_PATH="$LOCAL_VLC_PLUGINS:/usr/lib/vlc/plugins${VLC_PLUGIN_PATH:+:$VLC_PLUGIN_PATH}" -fi +# Note: no VLC_PLUGIN_PATH juggling. This system's VLC already ships the bluray +# access plugin (/usr/lib/vlc/plugins/access/liblibbluray_plugin.so); the old +# tools/vlc-plugins copy was only needed for split-package installs that lack it. + +# Give libbluray writable BD-J storage roots, and -- the important part -- put +# the BD-J VFS cache *under* the cache (BUDA) root. +# +# libbluray copies the disc's JAR and fonts into a VFS cache under java.io.tmpdir +# (default /tmp/libbluray-bdj-cache). Its BD-J SecurityManager.canWrite() checks +# roots in this order: budaRoot -> persistentRoot -> (if an Xlet context is +# active: DENY) -> cacheRoot. That extraction often runs while an Xlet context is +# live on the thread, so the write is denied before the cacheRoot allowance is +# ever reached -- the flaky "error creating .../libbluray-bdj-cache" / +# "failed to create temporary cache directory" cascade that stops the Xlet (menu) +# from loading. budaRoot/persistentRoot are allowed unconditionally, so pointing +# TMPDIR (== java.io.tmpdir == the VFS cache's base) at the buda root makes the +# cache path start with budaRoot and always pass the write check. +BDJ_RUN_DIR="$ROOT_DIR/build/bdj-runtime" +export LIBBLURAY_CACHE_ROOT="$BDJ_RUN_DIR/cache" +export LIBBLURAY_PERSISTENT_ROOT="$BDJ_RUN_DIR/persistent" +export TMPDIR="$LIBBLURAY_CACHE_ROOT" +mkdir -p "$LIBBLURAY_CACHE_ROOT" "$LIBBLURAY_PERSISTENT_ROOT" +# start each preview from a clean VFS cache +rm -rf "$LIBBLURAY_CACHE_ROOT/libbluray-bdj-cache" if [[ -d "$TARGET" ]]; then ABS_TARGET="$(cd "$TARGET" && pwd)" else ABS_TARGET="$(cd "$(dirname "$TARGET")" && pwd)/$(basename "$TARGET")" fi -exec vlc --reset-plugins-cache --bluray-menu "bluray://$ABS_TARGET" +exec vlc --bluray-menu "bluray://$ABS_TARGET" diff --git a/src/org/homebrew/AdjustmentsMenu.java b/src/org/homebrew/AdjustmentsMenu.java index 1e94bc7..7dc7e26 100644 --- a/src/org/homebrew/AdjustmentsMenu.java +++ b/src/org/homebrew/AdjustmentsMenu.java @@ -7,8 +7,15 @@ import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; +import javax.media.ControllerEvent; +import javax.media.ControllerListener; +import javax.media.EndOfMediaEvent; +import javax.media.Manager; +import javax.media.MediaLocator; +import javax.media.Player; +import javax.media.Time; -public class AdjustmentsMenu extends Container implements KeyListener { +public class AdjustmentsMenu extends Container implements KeyListener, ControllerListener { private static final String[] MAIN_ITEMS = { "MAIN FEATURE", "BONUS: WAITING ROOM OUTTAKES", @@ -30,6 +37,8 @@ public class AdjustmentsMenu extends Container implements KeyListener { private boolean hidden; private int codeProgress; private String status = "BD-J scaffold loaded"; + private Player featurePlayer; + private boolean menuShown = true; public AdjustmentsMenu() { setFocusable(true); @@ -111,7 +120,11 @@ public class AdjustmentsMenu extends Container implements KeyListener { selected = 0; status = "Returned to approved materials"; } else if ("MAIN FEATURE".equals(item)) { - status = "Main feature is playlist 00000"; + // The feature is already playing behind the menu; just hide the + // menu to reveal it. Press the menu key (or ENTER) to bring it back. + status = "Playing main feature -- press ENTER for menu"; + setMenuVisible(false); + return; } else if ("SETTINGS".equals(item)) { status = "Settings stub selected"; } else { @@ -121,6 +134,47 @@ public class AdjustmentsMenu extends Container implements KeyListener { repaint(); } + // Called once by the Xlet at startup. Start the feature playing straight + // away and leave it running for the whole session. This keeps VLC in normal + // "playing" mode with a live playlist behind the menu, so selecting an item + // never triggers the background->playlist transition that VLC botches + // ("blurayReleaseVout: subpicture channel exists", no video). The menu is + // just drawn on top and toggled with setMenuVisible(). + public void startPlayback() { + if (featurePlayer != null) { + return; + } + try { + // libbluray's "bd" protocol; PLAYLIST:00000 -> BDMV/PLAYLIST/00000.mpls. + featurePlayer = Manager.createPlayer(new MediaLocator("bd://PLAYLIST:00000")); + featurePlayer.addControllerListener(this); + featurePlayer.start(); + } catch (Exception e) { + featurePlayer = null; + status = "Playback failed: " + e.getMessage(); + } + repaint(); + } + + // Toggle only this menu Container, not the whole HScene, so the scene (and + // its key listener) stays alive while the video shows underneath. + private void setMenuVisible(boolean visible) { + menuShown = visible; + setVisible(visible); + if (visible) { + requestFocus(); + } + repaint(); + } + + public void controllerUpdate(ControllerEvent event) { + // Loop the feature so there is always a live playlist behind the menu. + if (event instanceof EndOfMediaEvent && featurePlayer != null) { + featurePlayer.setMediaTime(new Time(0)); + featurePlayer.start(); + } + } + private void unlockHidden() { hidden = true; selected = 0; @@ -131,6 +185,14 @@ public class AdjustmentsMenu extends Container implements KeyListener { public void keyPressed(KeyEvent e) { int code = e.getKeyCode(); + + // When the menu is hidden (feature playing full screen), any key brings + // the menu back over the still-running video. + if (!menuShown) { + setMenuVisible(true); + return; + } + String[] items = hidden ? HIDDEN_ITEMS : MAIN_ITEMS; if (code == KeyEvent.VK_UP) { diff --git a/src/org/homebrew/MyXlet.java b/src/org/homebrew/MyXlet.java index 711d371..1f4c209 100644 --- a/src/org/homebrew/MyXlet.java +++ b/src/org/homebrew/MyXlet.java @@ -29,6 +29,9 @@ public class MyXlet extends Thread implements Xlet { scene.setVisible(true); menu.requestFocus(); scene.requestFocus(); + // Start the feature immediately so VLC always has a live playlist behind + // the menu (avoids the background->playlist transition it botches). + menu.startPlayback(); start(); } diff --git a/vendor/bdj-sdk/samples/helloworld/.gitignore b/vendor/bdj-sdk/samples/helloworld/.gitignore new file mode 100644 index 0000000..deb367f --- /dev/null +++ b/vendor/bdj-sdk/samples/helloworld/.gitignore @@ -0,0 +1,6 @@ +*~ +*.class +discdir +META-INF +*.iso + diff --git a/vendor/bdj-sdk/samples/helloworld/Makefile b/vendor/bdj-sdk/samples/helloworld/Makefile new file mode 100644 index 0000000..1c85513 --- /dev/null +++ b/vendor/bdj-sdk/samples/helloworld/Makefile @@ -0,0 +1,80 @@ +# Copyright (C) 2022 John Törnblom +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not see +# . + + +DISC_LABEL := helloworld + +# +# Host tools +# +MAKEFILE_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) +BDJSDK_HOME ?= $(MAKEFILE_DIR)/../../ +BDSIGNER := $(BDJSDK_HOME)/host/bin/bdsigner +MAKEFS := $(BDJSDK_HOME)/host/bin/makefs +JAVA8_HOME ?= $(BDJSDK_HOME)/host/jdk8 +JAVAC := $(JAVA8_HOME)/bin/javac +JAR := $(JAVA8_HOME)/bin/jar + +export JAVA8_HOME + + +# +# Compilation artifacts +# +CLASSPATH := $(BDJSDK_HOME)/target/lib/enhanced-stubs.zip +SOURCES := $(wildcard src/org/homebrew/*.java) +JFLAGS := -Xlint:-options -source 1.3 -target 1.3 + + +# +# Disc files +# +TMPL_DIRS := $(shell find $(BDJSDK_HOME)/resources/AVCHD/ -type d) +TMPL_FILES := $(shell find $(BDJSDK_HOME)/resources/AVCHD/ -type f) + +DISC_DIRS := $(patsubst $(BDJSDK_HOME)/resources/AVCHD%,discdir%,$(TMPL_DIRS)) \ + discdir/BDMV/JAR +DISC_FILES := $(patsubst $(BDJSDK_HOME)/resources/AVCHD%,discdir%,$(TMPL_FILES)) \ + discdir/BDMV/JAR/00000.jar + + +all: $(DISC_LABEL).iso + + +discdir: + mkdir -p $(DISC_DIRS) + + +discdir/BDMV/JAR/00000.jar: discdir $(SOURCES) + $(JAVAC) $(JFLAGS) -cp $(CLASSPATH) $(SOURCES) + $(JAR) cf $@ -C src/ . + $(BDSIGNER) -keystore $(BDJSDK_HOME)/resources/sig.ks $@ + + +discdir/%: discdir + cp $(BDJSDK_HOME)/resources/AVCHD/$* $@ + + +$(DISC_LABEL).iso: $(DISC_FILES) + $(MAKEFS) -m 16m -t udf -o T=bdre,v=2.50,L=$(DISC_LABEL) $@ discdir + + +clean: + rm -rf META-INF $(DISC_LABEL).iso discdir src/org/homebrew/*.class + + +test: $(DISC_LABEL).iso + vlc $< diff --git a/vendor/bdj-sdk/samples/helloworld/src/org/homebrew/LoggingUI.java b/vendor/bdj-sdk/samples/helloworld/src/org/homebrew/LoggingUI.java new file mode 100644 index 0000000..df8be4b --- /dev/null +++ b/vendor/bdj-sdk/samples/helloworld/src/org/homebrew/LoggingUI.java @@ -0,0 +1,82 @@ +package org.homebrew; + +import java.awt.Color; +import java.awt.Container; +import java.awt.Font; +import java.awt.Graphics; +import java.io.StringWriter; +import java.io.PrintWriter; +import java.util.ArrayList; + +public class LoggingUI extends Container { + private final Font font = new Font(null, Font.PLAIN, 14); + private final Color fontColor = new Color(240, 240, 240); + private final Color bgColor = new Color(5, 5, 5); + private final ArrayList rows = new ArrayList(); + private static LoggingUI instance = null; + + private LoggingUI() { + } + + public static LoggingUI getInstance() { + if(instance == null) { + instance = new LoggingUI(); + } + + return instance; + } + + public void paint(Graphics g) { + g.setFont(font); + g.setColor(bgColor); + g.fillRect(20, 20, getWidth() - 40, getHeight() - 40); + g.setColor(fontColor); + + for(int i=0; i 45) { + rows.remove(0); + } + repaint(); + } + + public void log(Throwable t) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + StringBuffer sb = new StringBuffer(); + + t.printStackTrace(pw); + String st = sw.toString(); + + for(int i=0; i 0) { + log(sb.toString()); + } + } +} + diff --git a/vendor/bdj-sdk/samples/helloworld/src/org/homebrew/MyXlet.java b/vendor/bdj-sdk/samples/helloworld/src/org/homebrew/MyXlet.java new file mode 100644 index 0000000..9f97b20 --- /dev/null +++ b/vendor/bdj-sdk/samples/helloworld/src/org/homebrew/MyXlet.java @@ -0,0 +1,46 @@ +package org.homebrew; + +import java.awt.BorderLayout; +import javax.tv.xlet.Xlet; +import javax.tv.xlet.XletContext; +import org.havi.ui.HScene; +import org.havi.ui.HSceneFactory; + +public class MyXlet extends Thread implements Xlet { + private HScene scene; + private LoggingUI logUI; + + public void initXlet(XletContext context) { + logUI = LoggingUI.getInstance(); + logUI.setSize(1280, 720); + + scene = HSceneFactory.getInstance().getDefaultHScene(); + scene.add(logUI, BorderLayout.CENTER); + scene.validate(); + scene.repaint(); + } + + public void startXlet() { + logUI.setVisible(true); + scene.setVisible(true); + start(); + } + + public void pauseXlet() { + logUI.setVisible(false); + } + + public void destroyXlet(boolean unconditional) { + interrupt(); + logUI.setVisible(false); + scene.remove(logUI); + scene = null; + } + + public void run() { + logUI.log("Hello, World!"); + } +} + + + diff --git a/vendor/bdj-sdk/samples/helloworld/src/org/homebrew/bluray.MyXlet.perm b/vendor/bdj-sdk/samples/helloworld/src/org/homebrew/bluray.MyXlet.perm new file mode 100644 index 0000000..b225fb3 --- /dev/null +++ b/vendor/bdj-sdk/samples/helloworld/src/org/homebrew/bluray.MyXlet.perm @@ -0,0 +1,12 @@ + + + + + + + + + * + + + \ No newline at end of file