JMF playback, transparent graphics plane, macOS tsMuxeR

Wire up MAIN FEATURE to play playlist 00000 via JMF
Manager.createPlayer / prefetch / start. Clear the BD-J graphics
plane with AlphaComposite.Clear so the video plane shows through
during playback; redraw the menu on EndOfMediaEvent or Enter/Escape.

Claim exclusive key access through DVB EventManager so the player
firmware does not interpret arrow keys as transport controls.

Swap the Linux ELF tsMuxeR binary for the native macOS ARM64 build.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Olive Vaughn 2026-09-03 19:44:02 -04:00
parent 1b44001d44
commit ac846dc38f
3 changed files with 108 additions and 6 deletions

View file

@ -1,14 +1,27 @@
package org.homebrew; package org.homebrew;
import java.awt.AlphaComposite;
import java.awt.Color; import java.awt.Color;
import java.awt.Container; import java.awt.Container;
import java.awt.Font; import java.awt.Font;
import java.awt.FontMetrics; import java.awt.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.awt.event.KeyListener; 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;
import org.havi.ui.event.HRcEvent;
public class AdjustmentsMenu extends Container implements KeyListener, ControllerListener {
private Player player;
private boolean playing;
public class AdjustmentsMenu extends Container implements KeyListener {
private static final String[] MAIN_ITEMS = { private static final String[] MAIN_ITEMS = {
"MAIN FEATURE", "MAIN FEATURE",
"BONUS: WAITING ROOM OUTTAKES", "BONUS: WAITING ROOM OUTTAKES",
@ -38,6 +51,17 @@ public class AdjustmentsMenu extends Container implements KeyListener {
public void paint(Graphics g) { public void paint(Graphics g) {
int w = getWidth(); int w = getWidth();
int h = getHeight(); int h = getHeight();
Graphics2D g2 = (Graphics2D) g;
// Clear the graphics plane to transparent so the video plane shows
g2.setComposite(AlphaComposite.Clear);
g2.fillRect(0, 0, w, h);
g2.setComposite(AlphaComposite.SrcOver);
if (playing) {
return;
}
String[] items = hidden ? HIDDEN_ITEMS : MAIN_ITEMS; String[] items = hidden ? HIDDEN_ITEMS : MAIN_ITEMS;
g.setColor(new Color(8, 8, 10)); g.setColor(new Color(8, 8, 10));
@ -111,7 +135,7 @@ public class AdjustmentsMenu extends Container implements KeyListener {
selected = 0; selected = 0;
status = "Returned to approved materials"; status = "Returned to approved materials";
} else if ("MAIN FEATURE".equals(item)) { } else if ("MAIN FEATURE".equals(item)) {
status = "Main feature is playlist 00000"; playFeature();
} else if ("SETTINGS".equals(item)) { } else if ("SETTINGS".equals(item)) {
status = "Settings stub selected"; status = "Settings stub selected";
} else { } else {
@ -121,6 +145,29 @@ public class AdjustmentsMenu extends Container implements KeyListener {
repaint(); repaint();
} }
private void playFeature() {
try {
if (player == null) {
player = Manager.createPlayer(
new MediaLocator("bd://PLAYLIST:00000"));
player.addControllerListener(this);
} else {
player.setMediaTime(new Time(0));
}
player.prefetch();
player.start();
playing = true;
status = "Playing main feature";
} catch (Exception e) {
status = "Playback error: " + e.getMessage();
}
}
private void showMenu() {
playing = false;
repaint();
}
private void unlockHidden() { private void unlockHidden() {
hidden = true; hidden = true;
selected = 0; selected = 0;
@ -133,6 +180,13 @@ public class AdjustmentsMenu extends Container implements KeyListener {
int code = e.getKeyCode(); int code = e.getKeyCode();
String[] items = hidden ? HIDDEN_ITEMS : MAIN_ITEMS; String[] items = hidden ? HIDDEN_ITEMS : MAIN_ITEMS;
if (playing) {
if (code == KeyEvent.VK_ENTER || code == KeyEvent.VK_ESCAPE) {
showMenu();
}
return;
}
if (code == KeyEvent.VK_UP) { if (code == KeyEvent.VK_UP) {
selected = (selected + items.length - 1) % items.length; selected = (selected + items.length - 1) % items.length;
repaint(); repaint();
@ -151,10 +205,18 @@ public class AdjustmentsMenu extends Container implements KeyListener {
} }
} }
public void controllerUpdate(ControllerEvent event) {
if (event instanceof EndOfMediaEvent) {
showMenu();
} else {
status = "Event: " + event.getClass().getName();
repaint();
}
}
public void keyReleased(KeyEvent e) { public void keyReleased(KeyEvent e) {
} }
public void keyTyped(KeyEvent e) { public void keyTyped(KeyEvent e) {
} }
} }

View file

@ -1,13 +1,19 @@
package org.homebrew; package org.homebrew;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.event.KeyEvent;
import javax.tv.xlet.Xlet; import javax.tv.xlet.Xlet;
import javax.tv.xlet.XletContext; import javax.tv.xlet.XletContext;
import javax.tv.xlet.XletStateChangeException; import javax.tv.xlet.XletStateChangeException;
import org.dvb.event.EventManager;
import org.dvb.event.UserEvent;
import org.dvb.event.UserEventListener;
import org.dvb.event.UserEventRepository;
import org.havi.ui.HScene; import org.havi.ui.HScene;
import org.havi.ui.HSceneFactory; import org.havi.ui.HSceneFactory;
import org.havi.ui.event.HRcEvent;
public class MyXlet extends Thread implements Xlet { public class MyXlet extends Thread implements Xlet, UserEventListener {
private HScene scene; private HScene scene;
private AdjustmentsMenu menu; private AdjustmentsMenu menu;
private boolean running; private boolean running;
@ -18,9 +24,43 @@ public class MyXlet extends Thread implements Xlet {
scene = HSceneFactory.getInstance().getDefaultHScene(); scene = HSceneFactory.getInstance().getDefaultHScene();
scene.add(menu, BorderLayout.CENTER); scene.add(menu, BorderLayout.CENTER);
scene.addKeyListener(menu);
scene.validate(); scene.validate();
scene.repaint(); scene.repaint();
// Claim exclusive access to navigation and transport keys so the
// player firmware doesn't interpret them as FF/RW/skip.
UserEventRepository repo = new UserEventRepository("keys");
repo.addKey(KeyEvent.VK_UP);
repo.addKey(KeyEvent.VK_DOWN);
repo.addKey(KeyEvent.VK_LEFT);
repo.addKey(KeyEvent.VK_RIGHT);
repo.addKey(KeyEvent.VK_ENTER);
repo.addKey(KeyEvent.VK_SPACE);
repo.addKey(KeyEvent.VK_0);
repo.addKey(KeyEvent.VK_1);
repo.addKey(KeyEvent.VK_2);
repo.addKey(KeyEvent.VK_3);
repo.addKey(KeyEvent.VK_4);
repo.addKey(KeyEvent.VK_5);
repo.addKey(KeyEvent.VK_6);
repo.addKey(KeyEvent.VK_7);
repo.addKey(KeyEvent.VK_8);
repo.addKey(KeyEvent.VK_9);
repo.addKey(HRcEvent.VK_COLORED_KEY_0);
repo.addKey(HRcEvent.VK_COLORED_KEY_1);
repo.addKey(HRcEvent.VK_COLORED_KEY_2);
repo.addKey(HRcEvent.VK_COLORED_KEY_3);
EventManager.getInstance().addUserEventListener(this, repo);
}
public void userEventReceived(UserEvent event) {
if (event.getType() == KeyEvent.KEY_PRESSED) {
KeyEvent ke = new KeyEvent(
scene, KeyEvent.KEY_PRESSED,
event.getWhen(), event.getModifiers(),
event.getCode(), event.getKeyChar());
menu.keyPressed(ke);
}
} }
public void startXlet() throws XletStateChangeException { public void startXlet() throws XletStateChangeException {
@ -41,6 +81,7 @@ public class MyXlet extends Thread implements Xlet {
public void destroyXlet(boolean unconditional) { public void destroyXlet(boolean unconditional) {
running = false; running = false;
interrupt(); interrupt();
EventManager.getInstance().removeUserEventListener(this);
if (scene != null && menu != null) { if (scene != null && menu != null) {
menu.setVisible(false); menu.setVisible(false);
@ -61,4 +102,3 @@ public class MyXlet extends Thread implements Xlet {
} }
} }
} }

Binary file not shown.