0

Our videos use the lower third of the page for introductions, etc. much like TV News stations do. When captions are on, they're blocking all of that, thus creating a LOT of complaints from the communities that need the captions. I've tried tinkering with the CSS, but with a responsive layout, resizing the player wreaks havoc, often putting them out of sight altogether.

Is there a setting that can be changed, or technique to use, that will keep the captions at the top and in view when resized, OR in an external container?

enter image description here

GDP
  • 8,109
  • 6
  • 45
  • 82

2 Answers2

2

Problem: the JW player 608 live captions are not formatted cleanly. To solve this, disable the JW caption display and format our own window, named "ccbuffer"

<style type="text/css">
    .jw-captions {
           display: none !important;
    }
    #ccbuffer {
        border: 2px solid white !important;
        border-radius: 4px;
        background-color: black !important;
        display: flex;
        height: 120px;
        margin-top: 6px;
        font: 22px bold arial, sans-serif;
        color: white;
        justify-content: center;
        align-items: center;
    }
</style>

Here is where I show the player, and ccbuffer is a div right below it

    <div id="myPlayer">
        <p style="color: #FFFFFF; font-weight: bold; font-size: x-large; border-style: solid; border-color: #E2AA4F">
            Loading video...
        </p>
    </div>
    <div id="ccbuffer" /> 

DOMSubtreeModified is deprecated. Use MutationObserver, which is less stressful on the client. Let's hook the 'captionsChanged' event from JW. if track is 0 then no captions are selected and we disconnect the observer. If captions are selected, then we use jquery to pull the text out of the jw-text-track-cue element, and format it into a nice 3 line display in our ccbuffer window.

<script>
    var observer;

    jwplayer().on('captionsChanged', function (event) {
        if (event.track == 0) {
            observer.disconnect();
            $('#ccbuffer').hide('slow');
        }
        else {
            $('#ccbuffer').show('slow');

            // select the target node
            var target = document.querySelector('.jw-captions');

            // create an observer instance
            observer = new MutationObserver(function(mutations) {
                $('.jw-text-track-cue').each(function(i) {
                    if (i == 0)
                        $('#ccbuffer').html( $(this).text() );
                    else
                        $('#ccbuffer').append("<br/>" + $(this).text() );
                });
            });

            // configuration of the observer:
            var config = { attributes: true, childList: true, characterData: true }

            // pass in the target node, as well as the observer options
            observer.observe(target, config);
        }
    });

    $(document).ready(function () {
        $('#ccbuffer').hide();
    });

</script>

So when the user enables captions, the ccbuffer window will slide open and display a clean 3 line representation of the CC text.

image of jw player with captions

Basildane
  • 46
  • 3
  • This looks quite amazing, but I'm unable to give it a try for a few days. Once I do, it would appear that you'll be getting the thumbs up and accepted answer. Thank you SO much! – GDP Oct 31 '19 at 17:48
  • It just occurred to me, you are going to run into other issues with JW because things are not consistent. For example, the CaptionsChanged event is fired when the live player starts up so I use that to turn on/off the ccbuffer, but it DOESN'T fire on startup on VOD. Also, if the client is Safari, then captions are rendered natively. It's one nightmare after another with JW. Just hit me back if you get stuck. – Basildane Nov 01 '19 at 12:09
  • Drat, it's SO close. I'm having trouble with the initial show/hide, then toggling. As is always the case with JW, I suspect it will be setting a trail of flags along the initialization. Will have to look at it later, but in principal, this is exactly what I was looking for! – GDP Nov 01 '19 at 13:50
  • first, on Ready hide the box unless it's needed. – Basildane Nov 01 '19 at 14:42
  • $(document).ready(function () { $('#ccbuffer').hide(); }); – Basildane Nov 01 '19 at 14:42
  • Firstly, thanks again for your code. The JW Event to use to detect if CC is on or not, recordless of Live or VOD is `captionsList`. – GDP Nov 11 '19 at 17:23
1

Final Solution: External Captions that are draggable/resizable

All credit to @Basildane, I worked out how to extenalize the captions with VOD, and to make them draggable and resizable, with CSS experimentation for ADA consideration:

<!DOCTYPE html>
<html>
    <head>
        <title>JW External Captions</title>
        <meta http-equiv="Expires" content="Fri, Jan 01 1900 00:00:00 GMT">
        <meta http-equiv="Pragma" content="no-cache">
        <meta http-equiv="Cache-Control" content="no-cache">
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <meta http-equiv="Lang" content="en">
        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
        <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
        <script src="/jwplayer/v8.10/jwplayer.js"></script>
        <style type="text/css">
            #myPlayer {
                margin-bottom:5px;
            }
            .jw-captions {
                display: none !important;
            }
            #ccbuffer {
                color: white;
                background-color: black;
                opacity:.7;
                font: 22px bold san-serif;
                width: 100%;
                padding: 15px;
                height: 100%;
                position:relative;
            }
            .night {
                color:silver !important;
                background-color: black !important;
                opacity:1 !important;
                border-color:silver !important;
            }
            .highcontrast {
                color:white !   important;
                background-color: black !important;
                opacity:1 !important;
                border-color:white !important;
            }
            .highcontrast2 {
                color:black !important;
                background-color: yellow !important;
                opacity:1 !important;
                border-color:black !important;
            }
            .highcontrast3 {
                color:yellow !important;
                background-color: black !important;
                opacity:1 !important;
                border-color:yellow !important;
            }
            #ccContainer {
                position: absolute;
                z-index: 9;
                border: 1px solid inherit;
                overflow: hidden;
                resize: both;
                width: 640px;
                height: 180px;
                min-width: 120px;
                min-height: 90px;
                max-width: 960px;
                max-height: 300px;
            }
            #ccContainerheader {
                padding: 3px;
                cursor: move;
                z-index: 10;
                background-color: #2196F3;
                color: #fff;
                border:1px solid;
            }
        </style>
    </head>
    <body>
        <h3>JW Draggable Captions Container</h3>
        <div id="PlayerContainer" style="width:401px;">
            <div id="myPlayer">Loading video...</div>
        </div>
        <div id="ccContainer">
            <!-- Include a header DIV with the same name as the draggable DIV, followed by "header" -->
            <div style="float:right;">
                <form id="myform">
                    <select id="ccFontFamily">
                        <option value="sans-serif">Default Font</option>
                        <option value="serif">Serif</option>
                        <option value="monospace">Monospace</option>
                        <option value="cursive">Cursive </option>
                    </select>
                    <select id="ccFontSize" style="">
                        <option value="22">Default Size</option>
                        <option value="14">14</option>
                        <option value="18">18</option>
                        <option value="24">24</option>
                        <option value="32">32</option>
                    </select>
                    <select id="ccContrast" style="">
                        <option value="ccdefault">Default Contrast</option>
                        <option value="night">Night</option>
                        <option value="highcontrast">High Contrast</option>
                        <option value="highcontrast2">Black/Yellow</option>
                        <option value="highcontrast3">Yellow/Black</option>
                    </select>
                    <button id="ccFontReset">Reset</button>
                </form>
            </div>
            <div id="ccContainerheader">
                Captions (click to move)
            </div>
            <div id="ccbuffer"></div>
        </div>
        <script type="text/javascript">
            $(document).ready(function() {
                jwplayer.key = 'xxxxxxxxxxxxxxxxxxx';
                jwplayer('myPlayer').setup({
                    width: '100%', aspectratio: '16:9', repeat: 'false', autostart: 'false',
                    playlist: [{
                        sources: [ { file: 'https:www.example.com/video.mp4'}],
                        tracks: [ { file: 'https:www.example.com/video-captions.vtt', kind: 'captions', label: 'English', 'default': true } ]
                    }]
                })
                // External CC Container
                $('#ccContainer').hide();
                var position = $('#myPlayer').position();
                var width = $('#PlayerContainer').outerWidth();
                ccTop = position.top;
                ccLeft = (width+50)+'px'
                $('#ccContainer').css({'top':ccTop, left:ccLeft });
                var observer;
                jwplayer().on('captionsList', function (event) {
                    ccObserver(event);
                });
                jwplayer().on('captionsChanged', function (event) {
                    ccObserver(event);
                });
                videoplayer.on('fullscreen', function(event){
                    if(event.fullscreen){
                        $('.jw-captions').css('display','block');
                    }else{
             $('.jw-captions').css('display','none');
                    }
                });

                $("#ccFontFamily").change(function() {
                    $('#ccbuffer').css("font-family", $(this).val());
                });
                $("#ccFontSize").change(function() {
                    $('#ccbuffer').css("font-size", $(this).val() + "px");
                });
                $("#ccContrast").change(function() {
                    $('#ccContainer').removeClass("night highcontrast highcontrast2 highcontrast3").addClass( $(this).val() );
                    $('#ccContainerheader').removeClass("night highcontrast highcontrast2 highcontrast3").addClass( $(this).val() );
                    $('#ccbuffer').removeClass("night highcontrast highcontrast2 highcontrast3").addClass( $(this).val() );
                    $('select').removeClass("night highcontrast highcontrast2 highcontrast3").addClass( $(this).val() );
                    $('#ccFontReset').removeClass("night highcontrast highcontrast2 highcontrast3").addClass( $(this).val() );
                });
                $('#ccFontReset').click(function() {
                    ccFontReset();
                });
                function ccFontReset(){
                    $("#ccFontFamily").val($("#ccFontFamily option:first").val()).trigger('change');
                    $("#ccFontSize").val($("#ccFontSize option:first").val()).trigger('change');
                    $("#ccContrast").val($("#ccContrast option:first").val()).trigger('change');
                }
                ccFontReset();
            });
            function ccObserver(event){
                if (event.track == 0) {
                    $('#ccContainer').hide('slow');
         $('.jw-captions').css('display','block'); // VERY important
                    if (observer != null){
                        observer.disconnect();
                    }
                }
                else {
                    $('#ccContainer').show('slow');
         $('.jw-captions').css('display','none');  // VERY important
                    var target = document.querySelector('.jw-captions');
                    observer = new MutationObserver(function(mutations) {
                        $('.jw-text-track-cue').each(function(i) {
                            if (i == 0)
                                $('#ccbuffer').html( $(this).text() );
                            else
                                $('#ccbuffer').append("<br/>" + $(this).text() );
                        });
                    });
                    var config = { attributes: true, childList: true, characterData: true }
                    observer.observe(target, config);
                }
            }
            // External CC Container - Make the DIV element draggable:
            dragElement(document.getElementById("ccContainer"));
            function dragElement(elmnt) {
                var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
                if (document.getElementById(elmnt.id + "header")) {
                    document.getElementById(elmnt.id + "header").onmousedown = dragMouseDown;
                } else {
                    elmnt.onmousedown = dragMouseDown;
                }
                function dragMouseDown(e) {
                    e = e || window.event;
                    e.preventDefault();
                    pos3 = e.clientX;
                    pos4 = e.clientY;
                    document.onmouseup = closeDragElement;
                    document.onmousemove = elementDrag;
                }
                function elementDrag(e) {
                    e = e || window.event;
                    e.preventDefault();
                    pos1 = pos3 - e.clientX;
                    pos2 = pos4 - e.clientY;
                    pos3 = e.clientX;
                    pos4 = e.clientY;
                    elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
                    elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
                }
                function closeDragElement() {
                    document.onmouseup = null;
                    document.onmousemove = null;
                }
            }
        </script>
    </body>
</html>
GDP
  • 8,109
  • 6
  • 45
  • 82