Dual Subtitles for Jem.tv

Display two subtitle languages simultaneously on any Jem.tv video.

Works in seconds • No installation needed

How to Enable Dual Subtitles

Follow these simple steps to get started

1

Load a Video on Jem.tv

Go to videos.jem.tv, play any video, and enable your desired subtitle language using the CC button.

2

Open Developer Console

Press these keys on your keyboard to open the browser console:

Windows / Linux:

F12
or
Ctrl Shift J

Mac:

⌘ Cmd ⌥ Option J
3

Click on "Console" Tab

A panel will appear at the bottom or side of your screen. Make sure the Console tab is selected. You'll see a text area where you can type.

4

Copy & Paste the Script

Copy the script below (click the button), paste it into the console, and press Enter. A popup will ask you to choose your second language!

⚠️

WARNING: Read Before Copying!

Pasting code that you don't understand into a browser (especially if you are logged in, but also in general) can be devastating!

Make sure that you do NOT paste any code (including this code) into your browser before verifying with a professional computer programmer that it is safe to do so.

That being said, feel free to ask someone about this script's safety if you don't understand code.

!הצלחה

The Script

// Dual Subtitles Script for Jem.tv
(function() {
    const video = document.querySelector('video');
    if (!video) return alert('No video found!');
    
    const allTracks = Array.from(video.textTracks);
    const tracks = allTracks.filter(t => 
        t.kind === 'subtitles' && t.label && t.label.length > 0 && !t.label.toLowerCase().includes('shaka')
    );
    if (tracks.length < 2) return alert('Need at least 2 subtitle tracks!');
    
    // Choose layout mode
    const layoutChoice = prompt('Choose layout mode:\n\n1 = Stacked (one above the other)\n2 = Side-by-Side (left and right)\n\nEnter 1 or 2:', '1');
    if (layoutChoice === null) return;
    const sideBySide = layoutChoice.trim() === '2';
    
    // Show available languages
    const langs = tracks.map((t, i) => `${i}: ${t.label} (${t.language})`).join('\n');
    
    let leftTrack, rightTrack, secondTrack;
    
    if (sideBySide) {
        const leftIdx = prompt(`Choose LEFT subtitle language:\n${langs}\n\nEnter number:`, "0");
        if (leftIdx === null) return;
        leftTrack = tracks[parseInt(leftIdx)];
        if (!leftTrack) return alert('Invalid selection!');
        
        const rightIdx = prompt(`Choose RIGHT subtitle language:\n${langs}\n\nEnter number:`, "1");
        if (rightIdx === null) return;
        rightTrack = tracks[parseInt(rightIdx)];
        if (!rightTrack) return alert('Invalid selection!');
        
        // For side-by-side, we control both - hide all native tracks and native display
        tracks.forEach(t => t.mode = 'hidden');
        leftTrack.mode = 'hidden';
        rightTrack.mode = 'hidden';
    } else {
        const idx = prompt(`Choose SECOND subtitle language:\n${langs}\n\nEnter number:`, "0");
        if (idx === null) return;
        secondTrack = tracks[parseInt(idx)];
        if (!secondTrack) return alert('Invalid selection!');
        secondTrack.mode = 'hidden';
    }
    
    // Remove existing containers and styles
    document.getElementById('dual-sub-container')?.remove();
    document.getElementById('dual-sub-left')?.remove();
    document.getElementById('dual-sub-right')?.remove();
    document.getElementById('dual-sub-style')?.remove();
    
    // Hide native Jem.tv subtitle display
    const hideNativeStyle = document.createElement('style');
    hideNativeStyle.id = 'dual-sub-style';
    if (sideBySide) {
        // Hide all native subtitles for side-by-side
        hideNativeStyle.textContent = `
            app-player-subtitle .text-container.subtitles,
            app-player-subtitle app-text,
            .over-bottom-center app-player-subtitle { 
                display: none !important; 
            }
        `;
    }
    // For stacked mode, keep native subtitles visible (user's primary language)
    document.head.appendChild(hideNativeStyle);
    
    // Find the right player container
    const playerContainer = document.querySelector('jem-video-player') || 
                           document.querySelector('.vid-box') || 
                           video.closest('.vid-wrapper')?.parentElement ||
                           video.parentElement;
    playerContainer.style.position = 'relative';
    
    // Helper to sanitize subtitle text (allow only safe tags)
    function sanitizeSubtitle(text) {
        if (!text) return '';
        const temp = document.createElement('div');
        temp.innerHTML = text;
        temp.querySelectorAll('script').forEach(s => s.remove());
        return temp.innerHTML;
    }
    
    if (sideBySide) {
        // Create side-by-side container
        const wrapper = document.createElement('div');
        wrapper.id = 'dual-sub-container';
        
        const leftDiv = document.createElement('div');
        leftDiv.id = 'dual-sub-left';
        const rightDiv = document.createElement('div');
        rightDiv.id = 'dual-sub-right';
        
        wrapper.appendChild(leftDiv);
        wrapper.appendChild(rightDiv);
        playerContainer.appendChild(wrapper);
        
        function updateStyles() {
            const isFs = !!(document.fullscreenElement || document.webkitFullscreenElement);
            const fontSize = isFs ? 'clamp(22px, 2.8vw, 40px)' : '17px';
            const padding = isFs ? '12px 20px' : '6px 14px';
            const bottom = isFs ? '6%' : 'calc(18% - 15px)';
            const boxWidth = isFs ? '42vw' : '38vw';
            const gap = isFs ? '50px' : '30px';
            
            wrapper.style.cssText = `
                position: absolute;
                bottom: ${bottom};
                left: 50%;
                transform: translateX(-50%);
                display: flex;
                gap: ${gap};
                justify-content: center;
                align-items: flex-start;
                z-index: 2147483647;
                width: 95%;
                pointer-events: none;
            `;
            
            const baseStyle = `
                text-align: center;
                font-size: ${fontSize};
                color: white;
                text-shadow: 2px 2px 3px black, -1px -1px 2px black, 1px -1px 2px black, -1px 1px 2px black;
                padding: ${padding};
                background: rgba(0,0,0,0.7);
                border-radius: 6px;
                line-height: 1.4;
                width: ${boxWidth};
                min-height: 1.5em;
            `;
            
            leftDiv.style.cssText = baseStyle + 'direction: auto; unicode-bidi: plaintext;';
            rightDiv.style.cssText = baseStyle + 'direction: auto; unicode-bidi: plaintext;';
        }
        
        updateStyles();
        document.addEventListener('fullscreenchange', updateStyles);
        document.addEventListener('webkitfullscreenchange', updateStyles);
        
        // Delay timers to prevent flashing between sentences
        let leftTimer = null;
        let rightTimer = null;
        const CLEAR_DELAY = 1500; // 1.5 seconds
        
        leftTrack.oncuechange = function() {
            const cue = this.activeCues[0];
            if (cue) {
                clearTimeout(leftTimer);
                leftDiv.innerHTML = sanitizeSubtitle(cue.text);
            } else {
                leftTimer = setTimeout(() => { leftDiv.innerHTML = ''; }, CLEAR_DELAY);
            }
        };
        rightTrack.oncuechange = function() {
            const cue = this.activeCues[0];
            if (cue) {
                clearTimeout(rightTimer);
                rightDiv.innerHTML = sanitizeSubtitle(cue.text);
            } else {
                rightTimer = setTimeout(() => { rightDiv.innerHTML = ''; }, CLEAR_DELAY);
            }
        };
        
        console.log(`✅ Side-by-side subtitles enabled! Left: ${leftTrack.label}, Right: ${rightTrack.label}`);
        alert(`Side-by-side subtitles enabled!\nLeft: ${leftTrack.label}\nRight: ${rightTrack.label}`);
        
    } else {
        // Stacked mode (original behavior)
        const dualSubContainer = document.createElement('div');
        dualSubContainer.id = 'dual-sub-container';
        playerContainer.appendChild(dualSubContainer);
        
        function updateStyles() {
            const isFullscreen = !!(document.fullscreenElement || document.webkitFullscreenElement);
            
            if (isFullscreen) {
                dualSubContainer.style.cssText = `
                    position: absolute;
                    bottom: 10%;
                    left: 50%;
                    transform: translateX(-50%);
                    text-align: center;
                    z-index: 2147483647;
                    font-size: clamp(28px, 3vw, 44px);
                    color: white;
                    text-shadow: 2px 2px 3px black, -1px -1px 2px black, 1px -1px 2px black, -1px 1px 2px black, 0 0 10px black;
                    max-width: 85%;
                    pointer-events: none;
                    padding: 8px 18px;
                    background: rgba(0,0,0,0.7);
                    border-radius: 6px;
                    line-height: 1.3;
                    direction: auto;
                    unicode-bidi: plaintext;
                `;
            } else {
                dualSubContainer.style.cssText = `
                    position: absolute;
                    bottom: calc(22% - 20px);
                    left: 50%;
                    transform: translateX(-50%);
                    text-align: center;
                    z-index: 2147483647;
                    font-size: 18px;
                    color: white;
                    text-shadow: 1px 1px 2px black, -1px -1px 2px black, 1px -1px 2px black, -1px 1px 2px black;
                    max-width: 85%;
                    pointer-events: none;
                    padding: 4px 12px;
                    background: rgba(0,0,0,0.65);
                    border-radius: 4px;
                    line-height: 1.3;
                    direction: auto;
                    unicode-bidi: plaintext;
                `;
            }
        }
        
        updateStyles();
        document.addEventListener('fullscreenchange', updateStyles);
        document.addEventListener('webkitfullscreenchange', updateStyles);
        
        // Delay timer to prevent flashing between sentences
        let clearTimer = null;
        const CLEAR_DELAY = 1500; // 1.5 seconds
        
        secondTrack.oncuechange = function() {
            const cue = this.activeCues[0];
            if (cue) {
                clearTimeout(clearTimer);
                dualSubContainer.innerHTML = sanitizeSubtitle(cue.text);
            } else {
                clearTimer = setTimeout(() => { dualSubContainer.innerHTML = ''; }, CLEAR_DELAY);
            }
        };
        
        console.log(`✅ Dual subtitles enabled! Second language: ${secondTrack.label}`);
        alert(`Dual subtitles enabled!\nSecond language: ${secondTrack.label}`);
    }
})();
⚠️

Good to Know

You'll need to run this script again if you refresh the page or switch to a different video. The dual subtitles only work on videos that have multiple subtitle tracks available.

🔖

Prefer a One-Click Solution?

Don't want to open the console every time? Try the Bookmarklet Version — drag a button to your bookmarks bar and click it whenever you need dual subtitles. No typing required!

Frequently Asked Questions

Simply refresh the page, or paste this in the console:

document.getElementById('dual-sub-container')?.remove();
Not all videos on Jem.tv have multiple subtitle tracks. The video needs to have at least 2 subtitle languages available for this script to work.
Yes! The script only runs in your browser and doesn't send any data anywhere. It simply reads the existing subtitle tracks and displays a second one. You can read through the code yourself to verify.
Yes! In the script, you can modify the dualSubContainer.style.cssText section. Change bottom: 85px to adjust position, or color: white to change the text color.
It's tricky on mobile since you need access to the browser console. On Android, you can use browsers like Kiwi Browser that support extensions, or use remote debugging. On iOS, it's more limited.