Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

HLS player example

This example can be used for HLS playback of the following types of streams published on Web Call Server

  • WebRTC
  • RTMP
  • RTMFP

The example uses HLS player built in a browser and should be used with browsers supporting HLS, e.g. iOS Safari and Android Chrome.

Stream transcoding to HLS will start automatically when client requests the stream published on server

Table of Contents

Overview

The example shows how to convert stream published on WCS server to HLS and play it in browser using VideoJS library. HLS stream cut starts automatically when strea is requested by HLS URL, for example `https://demotest1.flashphoner.com:8445/e442test/e442test.m3u8 for the stream e442 on m3u8` on the screenshot below

Image Removed

In the URL on the screenshot, demo.flashphonr.com is the address of the WCS server.
Port 8445 is used for HLS over HTTPS.

Code of the example

The path to the source code of the example on WCS server is.

The example allows to choose VideoJS version to play. VideoJS 8 supports Low Latency HLS if server allows to play this format

Image Added

Image Added

Since build 2.0.244, the example supports the following parameters:

  • version - VideoJS version to use: videojs7 or videojs8 
  • src - stream full HLS URL to play, should be encoded with URI, for example https%3A%2F%2Ftest1.flashphoner.com%3A8445%2Ftest%2Ftest.m3u8 
  • autoplay - automatically play the HLS URL, in this case all the input fields and buttons are hidden: false (by default) or true 

The player URL example with parameters as displayed on the screenshot above (the link is available in Permalink field)

Code Block
themeRDark
https://test1.flashphoner.com:8444/client2/examples/demo/streaming/hls-player/hls-player.html?version=videojs7&src=https%3A%2F%2Ftest1.flashphoner.com%3A8445%2Ftest%2Ftest.m3u8

The player URL example with autoplay enabled

Code Block
themeRDark
https://test1.flashphoner.com:8444/client2/examples/demo/streaming/hls-player/hls-player.html?version=videojs7&src=https%3A%2F%2Ftest1.flashphoner.com%3A8445%2Ftest%2Ftest.m3u8&autoplay=true

In this case the stream should be played automatically with audio muted. A viewer should use a loud slider in the player interface to unmute audio.

The code of the example

The source code can be accessed on server by the following path:

/usr/local/FlashphonerWebCallServer/clientclient2/examples/demo/streaming/hls-player

hls-player.css - file with styles
video-js.css - file with HLS player stylesplayer page styles file
hls-player.html - player page of the player
hls-player.js - script or running the player
player launch script
player-page.html - common player page elements for three HLS playback examples

The videojs7 and videojs8 subfolders contain two VideoJS versions respectively:

video.js - script providing functionality of the player library script (http://videojs.com/, Apache License Version 2.0)
videojs-hlsvideo.min.js - script providing functionality of the player the player library script (minimized version)
This video-js.css - the player library styles file

The example can be tested using the following addressfiollowing URL:

httphttps://host:90918444/clientclient2/examples/demo/streaming/hls-player/hls-player.html

Here Where host is the address of the WCS server .address

...

Analyzing the code

To analyze the code , let's take the version of file get hls-player.js file version with hash 66cc3931703e13, which is available here and and can be downloaded with corresponding in build 2.0.5.28.2753.133.244.

1. Forming HLS server URL.getHLSUrl() Loading the player page

code

Code Block
languagejs
themeRDark
const loadPlayerPage = function initPage(() {
    if (videojsVersion) {
        $hideItem("videojsInputForm");
        loadVideoJS("#urlServervideojs" + videojsVersion).val(getHLSUrl());

    ...
}

2. Player initializing

videojs() code

...

;
    } else {
        let videojsInput = document.getElementById("videojsInput");
        for (videojsType in VIDEOJS_VERSION_TYPE) {
            let option = document.createElement("option");
            let videojsFolder = "";
            switch (videojsType) {
                case 'VIDEOJS7':
                    videojsFolder = VIDEOJS_VERSION_TYPE.VIDEOJS7;
                    break;
                case 'VIDEOJS8':
                    videojsFolder = VIDEOJS_VERSION_TYPE.VIDEOJS8;
                    break;
            }
            option.text = videojsFolder;
            option.value = videojsFolder;
            videojsInput.appendChild(option);
        }

        setHandler("videojsBtn", "click", onVideojsBtnClick);
    }
}

2. Loading the VideoJS chosen version

code

Code Block
languagejs
themeRDark
function initPage() {
    $("#urlServer").val(getHLSUrl());
    var player = videojs('remoteVideo');
    ...
}

3. Stream name definition (the stream must be published on server)

...

const loadVideoJS = function (version) {
    if (version) {
        let playerPage = document.getElementById("playerPage");
        loadFile(version + "/video.js", "text/javascript").then( data  => {
            console.log("HLS library loaded successfully", data);
            loadFile(version + "/video-js.css", "stylesheet").then ( data => {
                console.log("HLS library stylesheet loaded successfully", data);
                hideItem("videojsInputForm");
                loadPage("player-page.html", "playerPage", initPage );
            }).catch( err => {
                playerPage.innerHTML = "Can't load VideoJS library stylesheet";
                playerPage.setAttribute("class", "text-danger");
                console.error(err);
            })
        }).catch( err => {
            setText("videojsError", "Can't load VideoJS library");
            console.error(err);
        });
    }
}

3. The player HTML page initializing

code

Code Block
languagejs
themeRDark
const initPage = function initPage(() {
    if (playSrc) {
        setValue("fullLink", decodeURIComponent(playSrc));
    } else if (autoplay) {
        console...
    var applyFn = function () {
warn("No HLS URL set, autoplay disabled");
        autoplay = false;
    }
    let remoteVideo = document.getElementById('remoteVideo');
    if (autoplay) {
        // There should not be any visible item on the page unless player
        hideAllToAutoplay();
        // The player should use all available page width
        setUpPlayerItem(true);
        // The player should be muted to automatically start playback
        player = initVideoJsPlayer(remoteVideo, true);
        playBtnClick();
    } else {
         var streamName = $("#playStream").val();
        streamName = encodeURIComponent(streamName)// No autoplay, all the forms and buttons should be visible
        setText("header", "HLS VideoJS Player Minimal");
        displayCommonItems();
        setUpButtons();
        enablePlaybackStats();
        // The player should have a maximum fixed size
        setUpPlayerItem(false);
        // The player can be unmuted because user should click Play button
        player = initVideoJsPlayer(remoteVideo, false);
    }
}

4. Player initializing

videojs() code

The following parameters are passed to the player:

  • video - div tag to play the stream on the page
  • playsinline: true - play a video on the page without automatic full screen switching (ignored in iOS Safari)
  • playbackRates - playback rates list
  • liveui: true - enables rewind (DVR) interface
  • liveTracker - live playback thresholds setup
  • fill: true - scale the player to div tag dimensions
Code Block
languagejs
themeRDark
const initVideoJsPlayer = function(video, muted) {
    let videoJsPlayer = null;
    if (video) {
        video.className = "video-js vjs-default-skin";
        videoJsPlayer = videojs(video, {
            playsinline: true,
            playbackRates: [0.1, 0.25, 0.5, 1, 1.5, 2],
            liveui: true,
            liveTracker: {
                trackingThreshold: LIVE_THRESHOLD,
                liveTolerance: LIVE_TOLERANCE
            },
            fill: true,
            muted: muted
        });
        console.log("Using VideoJs " + videojs.VERSION);
        if (Browser.isSafariWebRTC() && Browser.isiOS()) {
            // iOS hack when using standard controls to leave fullscreen mode
            let videoTag = getActualVideoTag();
}

4. Forming HLS stream URL and player starting

player.play() 

...

            if(videoTag) {
                setWebkitFullscreenHandlers(videoTag, false);
            }
        }
    }
    return videoJsPlayer;
}

5. HLS stream URL forming

encodeURIComponent() code

If authentication key and token are set, they will be included to stream URL

Code Block
languagejs
themeRDark
function initPage(const getVideoSrc = function(src) {
    ...
let videoSrc = src;
 var applyFn = functionif (validateForm()) {
         ...let streamName = getValue('playStream');
        var srcstreamName = encodeURIComponent(streamName);
        videoSrc = $getValue("#urlServerurlServer").val() + "'/"' + streamName + "'/"' + streamName + "'.m3u8"';
        varlet key = $getValue('#keykey').val();
        varlet token = $getValue("#tokentoken").val();
        if (key.length > 0 && token.length > 0) {
            srcvideoSrc += "?" + key + "=" + token;
        }
    }
    setValue("fullLink", videoSrc);
    return videoSrc;
}

6. Player launching

player.on(), player.play() code

Code Block
languagejs
themeRDark
const playBtnClick = function() {
    let videoSrc = getVideoSrc(getValue("fullLink"));
    if (videoSrc) {
        player.on('loadedmetadata', function() {
            console.log("Play with VideoJs");
            player.play();
        });
        ...
        player.src({
            src: srcvideoSrc,
            type: "application/vnd.apple.mpegurl"
        });
        player.playonStarted();
    };
    $("#applyBtn").prop('disabled', false).click(applyFn);
}
}

7. playing event handler

player.on() code

Code Block
languagejs
themeRDark
const playBtnClick = function() {
     let videoSrc = getVideoSrc(getValue("fullLink"));
     if (videoSrc) {
         ...
        player.on('playing', function() {
            console.log("playing event fired");
            displayPermalink(videoSrc);
            if (player.liveTracker) {
                if (!player.liveTracker.isLive()) {
                    // A cratch to display live UI for the first subscriber
                    liveUIDisplay();
                }
                if (player.liveTracker.atLiveEdge()) {
                    // Unlock backward buttons when seeked to live edge
                    toggleBackButtons(true);
                    // Stop live UI startup timer
                    stopLiveUITimer();
                }
            }
        });
        ...
    }
}

8. Enable rewind (DVR) interface for the first subscriber

player.liveTracker.seekToLiveEdge() code

Code Block
languagejs
themeRDark
const liveUIDisplay = function() {
    stopLiveUITimer()
    if (player && player.liveTracker) {
        liveUITimer = setInterval(function() {
            if (!player.liveTracker.isLive() && player.liveTracker.liveWindow() > LIVE_THRESHOLD) {
                // Live UI is not displayed yet, seek to live edge to display
                player.liveTracker.seekToLiveEdge();
            }
        }, LIVE_UI_INTERVAL)
    }
}

9. Rewind button click action

player.seekable(), player.currentTime() code

Code Block
languagejs
themeRDark
const backBtnClick = function(event) {
    if (player != null && player.liveTracker) {
        toggleBackButtons(false);
        let seekable = player.seekable();
        let backTime = -1;
        if (event.target.id.indexOf("10") !== -1) {
            backTime = player.currentTime() - 10;
        } else if (event.target.id.indexOf("30") !== -1) {
            backTime = player.currentTime() - 30;
        }
        if (backTime < 0) {
            backTime = seekable ? seekable.start(0) : player.currentTime();
        }
        player.currentTime(backTime);
    }
}

10. Live button click action

player.liveTracker.seekToLiveEdge() code

Code Block
languagejs
themeRDark
const liveBtnClick = function() {
    if (player != null && player.liveTracker) {
        player.liveTracker.seekToLiveEdge();
        toggleBackButtons(true);
    }
}

11. Playback stopping

player.dispose() code

This method removes the div container tag where player was initialized from the page

Code Block
languagejs
themeRDark
const stopBtnClick = function() {
    if (player != null) {
        console.log("Stop VideoJS player");
        stopLiveUITimer();
        player.dispose();
    }
    onStopped();
}

12. New div container tag creation after previous player was removed

code

Code Block
languagejs
themeRDark
const createRemoteVideo = function(parent) {
    let remoteVideo = document.createElement("video");
    remoteVideo.id = "remoteVideo";
    remoteVideo.controls="controls";
    remoteVideo.autoplay="autoplay";
    remoteVideo.type="application/vnd.apple.mpegurl";
    remoteVideo.className = "video-js vjs-default-skin";
    remoteVideo.setAttribute("playsinline","");
    remoteVideo.setAttribute("webkit-playsinline","");
    parent.appendChild(remoteVideo);
    player = initVideoJsPlayer(remoteVideo, autoplay);
}

13. Getting an available playback statistics from HTML5 video tag

HTML5Stats code

Code Block
languagejs
themeRDark
const PlaybackStats = function(interval) {
    const playbackStats = {
        interval: interval || STATS_INTERVAL,
        timer: null,
        stats: null,
        start: function() {
            let video = getActualVideoTag();

            playbackStats.stop();
            stats = HTML5Stats(video);
            playbackStats.timer = setInterval(playbackStats.displayStats, playbackStats.interval);
            setText("videoWidth", "N/A");
            setText("videoHeight", "N/A");
            setText("videoRate", "N/A");
            setText("videoFps", "N/A");
            showItem("stats");
        },
        stop: function() {
            if (playbackStats.timer) {
                clearInterval(playbackStats.timer);
                playbackStats.timer = null;
            }
            playbackStats.stats = null;
            hideItem("stats");
        },
        displayStats: function() {
            if (stats.collect()) {
                let width = stats.getWidth();
                let height = stats.getHeight();
                let bitrate = stats.getBitrate();
                let fps = stats.getFps();

                setText("videoWidth", width);
                setText("videoHeight", height);

                if (bitrate !== undefined) {
                    setText("videoRate", Math.round(bitrate));
                }
                if (fps !== undefined) {
                    setText("videoFps", fps.toFixed(1));
                }
            }
        }
    };
    return playbackStats;
}