feat: redesign SCTE35Trigger with amber broadcast controls
This commit is contained in:
parent
b3685565ea
commit
4835f7c4b6
1 changed files with 102 additions and 17 deletions
|
|
@ -4,46 +4,131 @@ interface SCTE35TriggerProps {
|
||||||
onInject: (eventId: number, durationSeconds: number) => Promise<void>;
|
onInject: (eventId: number, durationSeconds: number) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LogEntry {
|
||||||
|
time: string;
|
||||||
|
eventId: number;
|
||||||
|
duration: number;
|
||||||
|
}
|
||||||
|
|
||||||
export function SCTE35Trigger({ onInject }: SCTE35TriggerProps) {
|
export function SCTE35Trigger({ onInject }: SCTE35TriggerProps) {
|
||||||
|
const [eventId, setEventId] = useState(1001);
|
||||||
const [duration, setDuration] = useState(30);
|
const [duration, setDuration] = useState(30);
|
||||||
const [isInjecting, setIsInjecting] = useState(false);
|
const [isInjecting, setIsInjecting] = useState(false);
|
||||||
const [lastInjected, setLastInjected] = useState<string | null>(null);
|
const [log, setLog] = useState<LogEntry[]>([]);
|
||||||
|
|
||||||
const handleInject = async () => {
|
const handleInject = async () => {
|
||||||
setIsInjecting(true);
|
setIsInjecting(true);
|
||||||
try {
|
try {
|
||||||
// Generate event ID from current timestamp (Date.now() & 0xFFFFFFFF ensures 32-bit)
|
|
||||||
const eventId = Date.now() & 0xFFFFFFFF;
|
|
||||||
await onInject(eventId, duration);
|
await onInject(eventId, duration);
|
||||||
setLastInjected(new Date().toLocaleTimeString());
|
const now = new Date();
|
||||||
|
const pad = (n: number) => String(n).padStart(2, '0');
|
||||||
|
const time = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
|
||||||
|
setLog(prev => [{ time, eventId, duration }, ...prev].slice(0, 8));
|
||||||
|
setEventId(prev => prev + 1);
|
||||||
} finally {
|
} finally {
|
||||||
setIsInjecting(false);
|
setIsInjecting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const inputStyle: React.CSSProperties = {
|
||||||
<div className="bg-gray-800 border border-gray-700 rounded-lg p-4">
|
background: 'rgba(0,0,0,0.35)',
|
||||||
<h3 className="text-white font-bold mb-3">SCTE35 Ad Marker</h3>
|
border: '1px solid var(--border)',
|
||||||
|
borderRadius: 3,
|
||||||
|
padding: '9px 12px',
|
||||||
|
fontFamily: 'var(--font-mono)',
|
||||||
|
fontSize: 13,
|
||||||
|
color: 'var(--text-primary)',
|
||||||
|
outline: 'none',
|
||||||
|
width: '100%',
|
||||||
|
};
|
||||||
|
|
||||||
<div className="mb-3">
|
const labelStyle: React.CSSProperties = {
|
||||||
<label className="text-gray-300 text-sm block mb-1">Duration (seconds)</label>
|
fontFamily: 'var(--font-ui)',
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '0.3em',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
color: 'var(--text-muted)',
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: 6,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label style={labelStyle}>Event ID</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min={1}
|
min={1} max={65535}
|
||||||
max={300}
|
value={eventId}
|
||||||
|
onChange={e => setEventId(Number(e.target.value))}
|
||||||
|
style={inputStyle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label style={labelStyle}>Duration (seconds)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min={1} max={600}
|
||||||
value={duration}
|
value={duration}
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setDuration(Number(e.target.value))}
|
onChange={e => setDuration(Number(e.target.value))}
|
||||||
className="w-full bg-gray-700 text-white border border-gray-600 rounded px-3 py-2"
|
style={inputStyle}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleInject}
|
onClick={handleInject}
|
||||||
disabled={isInjecting}
|
disabled={isInjecting}
|
||||||
className="w-full bg-orange-600 hover:bg-orange-700 disabled:bg-gray-600 text-white py-2 rounded font-medium"
|
style={{
|
||||||
|
background: isInjecting
|
||||||
|
? 'rgba(0,230,118,0.08)'
|
||||||
|
: 'linear-gradient(135deg, rgba(255,154,26,0.15), rgba(255,154,26,0.08))',
|
||||||
|
border: `1px solid ${isInjecting ? 'rgba(0,230,118,0.3)' : 'rgba(255,154,26,0.4)'}`,
|
||||||
|
color: isInjecting ? 'var(--green-safe)' : 'var(--accent-amber)',
|
||||||
|
fontFamily: 'var(--font-ui)',
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '0.2em',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
padding: 12,
|
||||||
|
borderRadius: 3,
|
||||||
|
cursor: isInjecting ? 'default' : 'pointer',
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: 8,
|
||||||
|
transition: 'all 0.15s',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{isInjecting ? 'Injecting...' : 'Inject Ad Break'}
|
<span>{isInjecting ? '✓' : '⬡'}</span>
|
||||||
|
<span>{isInjecting ? 'Injected' : 'Inject Ad Marker'}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{lastInjected && (
|
{log.length > 0 && (
|
||||||
<p className="text-gray-400 text-xs mt-2">Last injected: {lastInje
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, maxHeight: 160, overflowY: 'auto' }}>
|
||||||
|
{log.map((entry, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
|
fontFamily: 'var(--font-mono)', fontSize: 10,
|
||||||
|
color: 'var(--text-secondary)', letterSpacing: '0.04em',
|
||||||
|
display: 'flex', gap: 10,
|
||||||
|
padding: '4px 8px',
|
||||||
|
background: 'rgba(0,0,0,0.2)',
|
||||||
|
borderRadius: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ color: 'var(--dragon-blue)', flexShrink: 0 }}>{entry.time}</span>
|
||||||
|
<span style={{ color: 'var(--accent-amber)' }}>EVT#{entry.eventId}</span>
|
||||||
|
<span>{entry.duration}s splice</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue