<commit_before>(function () {
    'use strict';

    var API_SERVER = window.location.hostname + ':9003',
        SITE_URL = 'http://lunasync.ajf.me';

    var mode, socket, player, ytReady = false, errored = false;

    var state = {
        playing: false,
        current: null,
        playlist: []
    }, haveControl = false, chatNick = null;

    function $(id) {
        return document.getElementById(id);
    }

    window.onYouTubePlayerReady = function () {
        ytReady = true;
        player = $('player');
        // reload page if youtube player reloads to avoid initialising twice
        window.onYouTubePlayerReady = function () { window.location.reload(); };
    };

    window.onload = function () {
        var id, control;

        // get them before IE errors out
        if (!Object.prototype.hasOwnProperty.call(window, 'WebSocket')) {
            document.innerHTML = 'lunasync requires a browser that supports WebSocket, such as Google Chrome, Mozilla Firefox, Apple Safari, Opera or Internet Explorer 10. Sorry :(';
            return;
        }

        // disable document-hiding style
        $('site').className = '';

        // display basic UI elements present on all pages
        initBasic();

        // display correct page for URL
        if (window.location.pathname === '/') {
            mode = 'home';
            initHome();
        } else if (window.location.pathname[0] === '/') {
            mode = 'view';
            id = window.location.pathname.substr(1);
            // get secret from URL
            if (window.location.search.substr(0, 9) === '?control=') {
                control = window.location.search.substr(9);
            // get secret from localStorage if we had it backed up and redirect
            } else if (localStorage.getItem('secret-' + id) !== null) {
                control = localStorage.getItem('secret-' + id);
                window.location = '/' + id + '?control=' + control;
                return;
            }
            initView(id, control);
        } else {
            mode = '404';
            init404();
        }
    };

    function doAJAX(method, url, data, callback, errback) {
        var xhr;

        xhr = new XMLHttpRequest();
        xhr.open(method, 'http://' + API_SERVER + url);
        xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    callback(JSON.parse(xhr.responseText));
                } else {
                    errback(xhr);
                }
            }
        };
        if (data) {
            xhr.send(JSON.stringify(data));
        } else {
            xhr.send();
        }
    }

    function send(msg) {
        socket.send(JSON.stringify(msg));
    }

    function scrollChatlog() {
        $('chatlog').scrollTop = $('chatlog').scrollHeight;
    }

    function initBasic() {
        $('create-new').onclick = function () {
            var title;

            title = prompt('Enter a title:', '');
            if (title) {
                doAJAX('POST', '/new', {
                    title: title
                }, function (response) {
                    // back up the secret
                    localStorage.setItem('secret-' + response.stream.id, response.stream.secret);
                    window.location = '/' + response.stream.id + '?control=' + response.stream.secret;
                }, function (xhr) {
                    alert('Error while trying to create new sync:\nResponse code:\n' + xhr.status + '\nError message:' + xhr.responseText);
                });
            }
        };
    }

    // homepage
    function initHome() {
        // unhide home page
        $('homepage').className = '';
    }

    // sync viewing page
    function initView(id, control) {
        // unhide view page
        $('viewpage').className = '';

        if (!ytReady) {
            window.onYouTubePlayerReady = function () {
                player = $('player');
                // reload page if youtube player reloads to avoid initialising twice
                window.onYouTubePlayerReady = function () { window.location.reload(); };
                initRestView(id, control);
            };
        } else {
            initRestView(id, control);
        }
    }

    function initRestView(id, control) {
        var url;

        window.onStateChange = function (pstate) {
            var cueIndex;

            // if we are controlling
            if (haveControl) {
                // if video paused and it was playing according to known state
                if (pstate === 2 /* YT.PlayerState.PAUSED */ && state.playing) {
                    // broadcast state change
                    send({
                        type: 'stop',
                        time: player.getCurrentTime()
                    });
                    state.playing = false;
                // if video started playing and it was paused according to known state
                } else if (pstate === 1 /*YT.PlayerState.PLAYING*/ && !state.playing) {
                    // broadcast state change
                    send({
                        type: 'play',
                        time: player.getCurrentTime()
                    });
                    state.playing = true;
                // if the video ended
                } else if (pstate === 0 /*YT.PlayerState.ENDED*/) {
                    // if we haven't reached the end of the playlist
                    if (state.current + 1 < state.playlist.length) {
                        // cue next video
                        cueIndex = state.current + 1;
                    } else {
                        // cue first video
                        cueIndex = 0;
                    }

                    state.current = cueIndex;
                    state.playing = true;

                    send({
                        type: 'cue',
                        current: cueIndex
                    });

                    player.loadVideoById(state.playlist[cueIndex].id);

                    updatePlaylist();
                }
            }
        };
        player.addEventListener('onStateChange', 'onStateChange');

        socket = new WebSocket('ws://' + API_SERVER, ['lunasync']);
        socket.onopen = function () {
            // set our stream (subscribe to events) and get info on it
            send({
                type: 'set_stream',
                id: id,
                control: control
            });
        };
        socket.onerror = socket.onclose = function (err) {
            if (errored) {
                return;
            }
            $('viewpage').innerHTML = 'Error communicating with server, lost connection (server may be down, lunasync may have updated, try refreshing):\n' + err;
            errored = true;
        };
        socket.onmessage = function (event) {
            var msg, stream, elem;

            msg = JSON.parse(event.data);

            switch (msg.type) {
                case 'stream_info':
                    stream = msg.stream;

                    // display stream title
                    $('title').value = stream.title;
                    document.title = stream.title + ' - lunasync';

                    // display stream viewing URL
                    $('view-link').value = SITE_URL + '/' + stream.id;

                    // update state
                    state.playing = stream.playing;
                    state.current = stream.current;
                    state.playlist = stream.playlist;

                    // display playlist
                    updatePlaylist();

                    // cue and play correct video
                    state.playing = stream.playing;
                    state.current = stream.current;
                    if (state.current !== null) {
                        if (state.playing) {
                            player.loadVideoById(state.playlist[state.current].id, stream.time);
                        } else {
                            player.cueVideoById(state.playlist[state.current].id, stream.time);
                        }
                    }

                    // if we have control of the stream
                    if (msg.control) {
                        haveControl = true;

                        // allow changing title
                        $('title').disabled = false;
                        $('title').onchange = function () {
                            send({
                                type: 'change_title',
                                title: $('title').value
                            });
                            document.title = $('title').value + ' - lunasync';
                        };

                        // unhide control box
                        $('control').className = '';

                        // display stream control URL
                        $('control-link').value = SITE_URL + '/' + stream.id + '?control=' + control;

                        // enable stream controls
                        $('rm-button').disabled = false;
                        $('rm-button').onclick = function () {
                            var i, items = $('playlist').selectedOptions, oldCurrent, current;

                            oldCurrent = current = state.playlist[state.current];

                            // remove selected playlist items
                            for (i = 0; i < items.length; i++) {
                                // if currently playing video being deleted, switch to next
                                if (state.playlist[items[i].dataLSindex] === current) {
                                    // loop around
                                    if (items[i].dataLSindex + 1 < state.playlist.length) {
                                        current = state.playlist[items[i].dataLSindex + 1];
                                    } else {
                                        current = state.playlist[0];
                                    }
                                }
                                state.playlist.splice(items[i].dataLSindex, 1);
                            }

                            updatePlaylist();

                            // push update to server by overwriting playlist
                            // (this may create race conditions...)
                            send({
                                type: 'update_playlist',
                                playlist: state.playlist,
                                current: state.current
                            });

                            if (current !== state.playlist[state.current]) {
                                if (current !== null && state.playlist.indexOf(current) !== -1) {
                                    current = state.playlist.indexOf(current);
                                    send({
                                        type: 'cue',
                                        current: current
                                    });
                                } else {
                                    send({
                                        type: 'cue',
                                        current: null
                                    });
                                }
                            }
                        };

                        $('play-button').disabled = false;
                        $('play-button').onclick = $('playlist').ondblclick = function () {
                            var cueIndex;

                            if ($('playlist').selectedOptions.length === 0) {
                                cueIndex = 0;
                            } else {
                                cueIndex = $('playlist').selectedOptions[0].dataLSindex;
                            }

                            send({
                                type: 'cue',
                                current: cueIndex
                            });
                        };

                        $('move-up-btn').disabled = false;
                        $('move-up-btn').onclick = function () {
                            var i, items = $('playlist').selectedOptions, item, current;

                            current = state.playlist[state.current];

                            // move selected playlist items up
                            for (i = 0; i < items.length; i++) {
                                if (items[i].dataLSindex - 1 >= 0) {
                                    item = state.playlist.splice(items[i].dataLSindex, 1);
                                    state.playlist.splice(items[i].dataLSindex - 1, 0, item[0]);
                                }
                            }

                            if (current !== state.playlist[state.current]) {
                                state.current = state.playlist.indexOf(current);
                            }

                            updatePlaylist();

                            // push update to server by overwriting playlist
                            // (this may create race conditions...)
                            send({
                                type: 'update_playlist',
                                playlist: state.playlist,
                                current: state.current
                            });
                        };

                        $('move-down-btn').disabled = false;
                        $('move-down-btn').onclick = function () {
                            var i, items = $('playlist').selectedOptions, item, current;

                            current = state.playlist[state.current];

                            // move selected playlist items up
                            for (i = 0; i < items.length; i++) {
                                if (items[i].dataLSindex + 1 < state.playlist.length) {
                                    item = state.playlist.splice(items[i].dataLSindex, 1);
                                    state.playlist.splice(items[i].dataLSindex + 1, 0, item[0]);
                                }
                            }

                            if (current !== state.playlist[state.current]) {
                                state.current = state.playlist.indexOf(current);
                            }

                            updatePlaylist();

                            // push update to server by overwriting playlist
                            // (this may create race conditions...)
                            send({
                                type: 'update_playlist',
                                playlist: state.playlist,
                                current: state.current
                            });
                        };

                        $('add-url').disabled = false;
                        $('add-url').onkeypress = function (e) {
                            var videoID;

                            // enter
                            if (e.which === 13) {
                                e.preventDefault();
                                $('add-url').blur();
                                videoID = getVideoID($('add-url').value);
                                if (videoID !== false) {
                                    send({
                                        type: 'add_url',
                                        id: videoID
                                    });
                                    $('add-url').value = '';
                                } else {
                                    alert($('add-url').value + ' is not a valid youtube URL!');
                                }
                                return false;
                            }
                        };
                        $('playlist').disabled = false;
                    } else {
                        // only enable re-sync button if not in control
                        // (otherwise is a fairly fruitless exercise)
                        $('re-sync-btn').disabled = false;
                        $('re-sync-btn').onclick = function () {
                            send({
                                type: 're_sync'
                            });
                        };
                    }

                    // make chat work
                    $('chatbox').placeholder = 'choose a nick (press enter)';
                    $('chatbox').onkeypress = function (e) {
                        // enter
                        if (e.which === 13) {
                            e.preventDefault();
                            if (chatNick === null) {
                                send({
                                    type: 'set_nick',
                                    nick: $('chatbox').value
                                });
                                $('chatbox').disabled = true;
                            } else {
                                send({
                                    type: 'msg',
                                    msg: $('chatbox').value
                                });
                            }
                            $('chatbox').value = '';
                            return false;
                        }
                    };
                    window.onresize = scrollChatlog;
                break;
                case 'update_playlist':
                    state.playlist = msg.playlist;
                    state.current = msg.current;
                    updatePlaylist();
                break;
                case 'change_title':
                    $('title').value = msg.title;
                    document.title = msg.title + ' - lunasync';
                break;
                case 'cue':
                    state.playing = true;
                    state.current = msg.current;
                    if (state.current === null) {
                        player.cueVideoById('');
                    } else {
                        player.loadVideoById(state.playlist[state.current].id);
                    }
                    updatePlaylist();
                break;
                case 'play':
                    state.playing = true;
                    player.seekTo(msg.time, true);
                    player.playVideo();
                break;
                case 'stop':
                    state.playing = false;
                    player.seekTo(msg.time, true);
                    player.pauseVideo();
                break;
                case 'join':
                    elem = document.createElement('div');
                    elem.appendChild(document.createTextNode(msg.nick + ' joined chat'));
                    $('chatlog').appendChild(elem);
                    scrollChatlog();
                break;
                case 'leave':
                    elem = document.createElement('div');
                    elem.appendChild(document.createTextNode(msg.nick + ' left chat'));
                    $('chatlog').appendChild(elem);
                    scrollChatlog();
                break;
                case 'msg':
                    elem = document.createElement('div');
                    elem.appendChild(document.createTextNode(msg.nick + ': ' + msg.msg));
                    $('chatlog').appendChild(elem);
                    scrollChatlog();
                break;
                case 'chat_info':
                    elem = document.createElement('div');
                    elem.appendChild(document.createTextNode(msg.msg));
                    $('chatlog').appendChild(elem);
                    scrollChatlog();
                break;
                case 'nick_taken':
                    alert('The nick "' + msg.nick + '" ids already taken - choose another one!');
                    $('chatbox').disabled = false;
                break;
                case 'nick_chosen':
                    $('chatbox').placeholder = 'say something (press enter)';
                    chatNick = msg.nick;
                    $('chatbox').disabled = false;
                break;
                case 'error':
                    if (msg.error === 'not_found') {
                        document.title = 'stream not found - lunasync';
                        $('viewpage').innerHTML = 'Error: no such stream exists. Did you copy the URL correctly?';
                    } else {
                        $('viewpage').innerHTML = 'Error communicating with server, lost connection:\n' + msg.error;
                    }
                    errored = true;
                break;
            }
        };
    }

    // "404" page
    function init404() {
        document.title = '404 not found - lunasync';
        // unhide 404 page
        $('page404').className = '';
    }

    function updatePlaylist() {
        var i, option;

        $('playlist').innerHTML = '';
        for (i = 0; i < state.playlist.length; i++) {
            option = document.createElement('option');
            if (state.current === i) {
                option.appendChild(document.createTextNode('▶ '));
                option.className = 'now-playing';
            }
            option.appendChild(document.createTextNode(state.playlist[i].title));
            option.dataLSindex = i;
            $('playlist').appendChild(option);
        }
    }

    function getVideoID(url) {
        var pos, pos2;

        if (url.substr(0, 7) === 'http://') {
            url = url.substr(7);
        } else if (url.substr(0, 8) === 'https://') {
            url = url.substr(8);
        } else {
            return false;
        }
        if (url.substr(0, 4) === 'www.') {
            url = url.substr(4);
        }
        if (url.substr(0, 9) === 'youtu.be/') {
            return url.substr(9);
        } else if (url.substr(0, 18) === 'youtube.com/watch?') {
            url = url.substr(18);
            pos = url.indexOf('v=');
            if (pos !== -1) {
                url = url.substr(pos + 2);
                pos2 = url.indexOf('&');
                if (pos2 === -1) {
                    return url;
                } else {
                    return url.substr(0, pos2);
                }
            } else {
                return false;
            }
        }
    }
}());
<commit_msg>don't try to load video if playlist empty<commit_after>(function () {
    'use strict';

    var API_SERVER = window.location.hostname + ':9003',
        SITE_URL = 'http://lunasync.ajf.me';

    var mode, socket, player, ytReady = false, errored = false;

    var state = {
        playing: false,
        current: null,
        playlist: []
    }, haveControl = false, chatNick = null;

    function $(id) {
        return document.getElementById(id);
    }

    window.onYouTubePlayerReady = function () {
        ytReady = true;
        player = $('player');
        // reload page if youtube player reloads to avoid initialising twice
        window.onYouTubePlayerReady = function () { window.location.reload(); };
    };

    window.onload = function () {
        var id, control;

        // get them before IE errors out
        if (!Object.prototype.hasOwnProperty.call(window, 'WebSocket')) {
            document.innerHTML = 'lunasync requires a browser that supports WebSocket, such as Google Chrome, Mozilla Firefox, Apple Safari, Opera or Internet Explorer 10. Sorry :(';
            return;
        }

        // disable document-hiding style
        $('site').className = '';

        // display basic UI elements present on all pages
        initBasic();

        // display correct page for URL
        if (window.location.pathname === '/') {
            mode = 'home';
            initHome();
        } else if (window.location.pathname[0] === '/') {
            mode = 'view';
            id = window.location.pathname.substr(1);
            // get secret from URL
            if (window.location.search.substr(0, 9) === '?control=') {
                control = window.location.search.substr(9);
            // get secret from localStorage if we had it backed up and redirect
            } else if (localStorage.getItem('secret-' + id) !== null) {
                control = localStorage.getItem('secret-' + id);
                window.location = '/' + id + '?control=' + control;
                return;
            }
            initView(id, control);
        } else {
            mode = '404';
            init404();
        }
    };

    function doAJAX(method, url, data, callback, errback) {
        var xhr;

        xhr = new XMLHttpRequest();
        xhr.open(method, 'http://' + API_SERVER + url);
        xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    callback(JSON.parse(xhr.responseText));
                } else {
                    errback(xhr);
                }
            }
        };
        if (data) {
            xhr.send(JSON.stringify(data));
        } else {
            xhr.send();
        }
    }

    function send(msg) {
        socket.send(JSON.stringify(msg));
    }

    function scrollChatlog() {
        $('chatlog').scrollTop = $('chatlog').scrollHeight;
    }

    function initBasic() {
        $('create-new').onclick = function () {
            var title;

            title = prompt('Enter a title:', '');
            if (title) {
                doAJAX('POST', '/new', {
                    title: title
                }, function (response) {
                    // back up the secret
                    localStorage.setItem('secret-' + response.stream.id, response.stream.secret);
                    window.location = '/' + response.stream.id + '?control=' + response.stream.secret;
                }, function (xhr) {
                    alert('Error while trying to create new sync:\nResponse code:\n' + xhr.status + '\nError message:' + xhr.responseText);
                });
            }
        };
    }

    // homepage
    function initHome() {
        // unhide home page
        $('homepage').className = '';
    }

    // sync viewing page
    function initView(id, control) {
        // unhide view page
        $('viewpage').className = '';

        if (!ytReady) {
            window.onYouTubePlayerReady = function () {
                player = $('player');
                // reload page if youtube player reloads to avoid initialising twice
                window.onYouTubePlayerReady = function () { window.location.reload(); };
                initRestView(id, control);
            };
        } else {
            initRestView(id, control);
        }
    }

    function initRestView(id, control) {
        var url;

        window.onStateChange = function (pstate) {
            var cueIndex;

            // if we are controlling
            if (haveControl) {
                // if video paused and it was playing according to known state
                if (pstate === 2 /* YT.PlayerState.PAUSED */ && state.playing) {
                    // broadcast state change
                    send({
                        type: 'stop',
                        time: player.getCurrentTime()
                    });
                    state.playing = false;
                // if video started playing and it was paused according to known state
                } else if (pstate === 1 /*YT.PlayerState.PLAYING*/ && !state.playing) {
                    // broadcast state change
                    send({
                        type: 'play',
                        time: player.getCurrentTime()
                    });
                    state.playing = true;
                // if the video ended
                } else if (pstate === 0 /*YT.PlayerState.ENDED*/) {
                    // if we haven't reached the end of the playlist
                    if (state.current + 1 < state.playlist.length) {
                        // cue next video
                        cueIndex = state.current + 1;
                    } else {
                        // cue first video
                        cueIndex = 0;
                    }

                    state.current = cueIndex;
                    state.playing = true;

                    send({
                        type: 'cue',
                        current: cueIndex
                    });

                    player.loadVideoById(state.playlist[cueIndex].id);

                    updatePlaylist();
                }
            }
        };
        player.addEventListener('onStateChange', 'onStateChange');

        socket = new WebSocket('ws://' + API_SERVER, ['lunasync']);
        socket.onopen = function () {
            // set our stream (subscribe to events) and get info on it
            send({
                type: 'set_stream',
                id: id,
                control: control
            });
        };
        socket.onerror = socket.onclose = function (err) {
            if (errored) {
                return;
            }
            $('viewpage').innerHTML = 'Error communicating with server, lost connection (server may be down, lunasync may have updated, try refreshing):\n' + err;
            errored = true;
        };
        socket.onmessage = function (event) {
            var msg, stream, elem;

            msg = JSON.parse(event.data);

            switch (msg.type) {
                case 'stream_info':
                    stream = msg.stream;

                    // display stream title
                    $('title').value = stream.title;
                    document.title = stream.title + ' - lunasync';

                    // display stream viewing URL
                    $('view-link').value = SITE_URL + '/' + stream.id;

                    // update state
                    state.playing = stream.playing;
                    state.current = stream.current;
                    state.playlist = stream.playlist;

                    // display playlist
                    updatePlaylist();

                    // cue and play correct video
                    state.playing = stream.playing;
                    state.current = stream.current;
                    if (state.current !== null && state.playlist.length) {
                        if (state.playing) {
                            player.loadVideoById(state.playlist[state.current].id, stream.time);
                        } else {
                            player.cueVideoById(state.playlist[state.current].id, stream.time);
                        }
                    }

                    // if we have control of the stream
                    if (msg.control) {
                        haveControl = true;

                        // allow changing title
                        $('title').disabled = false;
                        $('title').onchange = function () {
                            send({
                                type: 'change_title',
                                title: $('title').value
                            });
                            document.title = $('title').value + ' - lunasync';
                        };

                        // unhide control box
                        $('control').className = '';

                        // display stream control URL
                        $('control-link').value = SITE_URL + '/' + stream.id + '?control=' + control;

                        // enable stream controls
                        $('rm-button').disabled = false;
                        $('rm-button').onclick = function () {
                            var i, items = $('playlist').selectedOptions, oldCurrent, current;

                            oldCurrent = current = state.playlist[state.current];

                            // remove selected playlist items
                            for (i = 0; i < items.length; i++) {
                                // if currently playing video being deleted, switch to next
                                if (state.playlist[items[i].dataLSindex] === current) {
                                    // loop around
                                    if (items[i].dataLSindex + 1 < state.playlist.length) {
                                        current = state.playlist[items[i].dataLSindex + 1];
                                    } else {
                                        current = state.playlist[0];
                                    }
                                }
                                state.playlist.splice(items[i].dataLSindex, 1);
                            }

                            updatePlaylist();

                            // push update to server by overwriting playlist
                            // (this may create race conditions...)
                            send({
                                type: 'update_playlist',
                                playlist: state.playlist,
                                current: state.current
                            });

                            if (current !== state.playlist[state.current]) {
                                if (current !== null && state.playlist.indexOf(current) !== -1) {
                                    current = state.playlist.indexOf(current);
                                    send({
                                        type: 'cue',
                                        current: current
                                    });
                                } else {
                                    send({
                                        type: 'cue',
                                        current: null
                                    });
                                }
                            }
                        };

                        $('play-button').disabled = false;
                        $('play-button').onclick = $('playlist').ondblclick = function () {
                            var cueIndex;

                            if ($('playlist').selectedOptions.length === 0) {
                                cueIndex = 0;
                            } else {
                                cueIndex = $('playlist').selectedOptions[0].dataLSindex;
                            }

                            send({
                                type: 'cue',
                                current: cueIndex
                            });
                        };

                        $('move-up-btn').disabled = false;
                        $('move-up-btn').onclick = function () {
                            var i, items = $('playlist').selectedOptions, item, current;

                            current = state.playlist[state.current];

                            // move selected playlist items up
                            for (i = 0; i < items.length; i++) {
                                if (items[i].dataLSindex - 1 >= 0) {
                                    item = state.playlist.splice(items[i].dataLSindex, 1);
                                    state.playlist.splice(items[i].dataLSindex - 1, 0, item[0]);
                                }
                            }

                            if (current !== state.playlist[state.current]) {
                                state.current = state.playlist.indexOf(current);
                            }

                            updatePlaylist();

                            // push update to server by overwriting playlist
                            // (this may create race conditions...)
                            send({
                                type: 'update_playlist',
                                playlist: state.playlist,
                                current: state.current
                            });
                        };

                        $('move-down-btn').disabled = false;
                        $('move-down-btn').onclick = function () {
                            var i, items = $('playlist').selectedOptions, item, current;

                            current = state.playlist[state.current];

                            // move selected playlist items up
                            for (i = 0; i < items.length; i++) {
                                if (items[i].dataLSindex + 1 < state.playlist.length) {
                                    item = state.playlist.splice(items[i].dataLSindex, 1);
                                    state.playlist.splice(items[i].dataLSindex + 1, 0, item[0]);
                                }
                            }

                            if (current !== state.playlist[state.current]) {
                                state.current = state.playlist.indexOf(current);
                            }

                            updatePlaylist();

                            // push update to server by overwriting playlist
                            // (this may create race conditions...)
                            send({
                                type: 'update_playlist',
                                playlist: state.playlist,
                                current: state.current
                            });
                        };

                        $('add-url').disabled = false;
                        $('add-url').onkeypress = function (e) {
                            var videoID;

                            // enter
                            if (e.which === 13) {
                                e.preventDefault();
                                $('add-url').blur();
                                videoID = getVideoID($('add-url').value);
                                if (videoID !== false) {
                                    send({
                                        type: 'add_url',
                                        id: videoID
                                    });
                                    $('add-url').value = '';
                                } else {
                                    alert($('add-url').value + ' is not a valid youtube URL!');
                                }
                                return false;
                            }
                        };
                        $('playlist').disabled = false;
                    } else {
                        // only enable re-sync button if not in control
                        // (otherwise is a fairly fruitless exercise)
                        $('re-sync-btn').disabled = false;
                        $('re-sync-btn').onclick = function () {
                            send({
                                type: 're_sync'
                            });
                        };
                    }

                    // make chat work
                    $('chatbox').placeholder = 'choose a nick (press enter)';
                    $('chatbox').onkeypress = function (e) {
                        // enter
                        if (e.which === 13) {
                            e.preventDefault();
                            if (chatNick === null) {
                                send({
                                    type: 'set_nick',
                                    nick: $('chatbox').value
                                });
                                $('chatbox').disabled = true;
                            } else {
                                send({
                                    type: 'msg',
                                    msg: $('chatbox').value
                                });
                            }
                            $('chatbox').value = '';
                            return false;
                        }
                    };
                    window.onresize = scrollChatlog;
                break;
                case 'update_playlist':
                    state.playlist = msg.playlist;
                    state.current = msg.current;
                    updatePlaylist();
                break;
                case 'change_title':
                    $('title').value = msg.title;
                    document.title = msg.title + ' - lunasync';
                break;
                case 'cue':
                    state.playing = true;
                    state.current = msg.current;
                    if (state.current === null) {
                        player.cueVideoById('');
                    } else {
                        player.loadVideoById(state.playlist[state.current].id);
                    }
                    updatePlaylist();
                break;
                case 'play':
                    state.playing = true;
                    player.seekTo(msg.time, true);
                    player.playVideo();
                break;
                case 'stop':
                    state.playing = false;
                    player.seekTo(msg.time, true);
                    player.pauseVideo();
                break;
                case 'join':
                    elem = document.createElement('div');
                    elem.appendChild(document.createTextNode(msg.nick + ' joined chat'));
                    $('chatlog').appendChild(elem);
                    scrollChatlog();
                break;
                case 'leave':
                    elem = document.createElement('div');
                    elem.appendChild(document.createTextNode(msg.nick + ' left chat'));
                    $('chatlog').appendChild(elem);
                    scrollChatlog();
                break;
                case 'msg':
                    elem = document.createElement('div');
                    elem.appendChild(document.createTextNode(msg.nick + ': ' + msg.msg));
                    $('chatlog').appendChild(elem);
                    scrollChatlog();
                break;
                case 'chat_info':
                    elem = document.createElement('div');
                    elem.appendChild(document.createTextNode(msg.msg));
                    $('chatlog').appendChild(elem);
                    scrollChatlog();
                break;
                case 'nick_taken':
                    alert('The nick "' + msg.nick + '" ids already taken - choose another one!');
                    $('chatbox').disabled = false;
                break;
                case 'nick_chosen':
                    $('chatbox').placeholder = 'say something (press enter)';
                    chatNick = msg.nick;
                    $('chatbox').disabled = false;
                break;
                case 'error':
                    if (msg.error === 'not_found') {
                        document.title = 'stream not found - lunasync';
                        $('viewpage').innerHTML = 'Error: no such stream exists. Did you copy the URL correctly?';
                    } else {
                        $('viewpage').innerHTML = 'Error communicating with server, lost connection:\n' + msg.error;
                    }
                    errored = true;
                break;
            }
        };
    }

    // "404" page
    function init404() {
        document.title = '404 not found - lunasync';
        // unhide 404 page
        $('page404').className = '';
    }

    function updatePlaylist() {
        var i, option;

        $('playlist').innerHTML = '';
        for (i = 0; i < state.playlist.length; i++) {
            option = document.createElement('option');
            if (state.current === i) {
                option.appendChild(document.createTextNode('▶ '));
                option.className = 'now-playing';
            }
            option.appendChild(document.createTextNode(state.playlist[i].title));
            option.dataLSindex = i;
            $('playlist').appendChild(option);
        }
    }

    function getVideoID(url) {
        var pos, pos2;

        if (url.substr(0, 7) === 'http://') {
            url = url.substr(7);
        } else if (url.substr(0, 8) === 'https://') {
            url = url.substr(8);
        } else {
            return false;
        }
        if (url.substr(0, 4) === 'www.') {
            url = url.substr(4);
        }
        if (url.substr(0, 9) === 'youtu.be/') {
            return url.substr(9);
        } else if (url.substr(0, 18) === 'youtube.com/watch?') {
            url = url.substr(18);
            pos = url.indexOf('v=');
            if (pos !== -1) {
                url = url.substr(pos + 2);
                pos2 = url.indexOf('&');
                if (pos2 === -1) {
                    return url;
                } else {
                    return url.substr(0, pos2);
                }
            } else {
                return false;
            }
        }
    }
}());
<|endoftext|>


<commit_before>// This file is part of Product Opener.
//
// Product Opener
// Copyright (C) 2011-2019 Association Open Food Facts
// Contact: contact@openfoodfacts.org
// Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France
//
// Product Opener is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

/* eslint-disable no-undefined */
/*exported lang countries*/

function doWebShare(e) {
  e.preventDefault();

  if (!window.isSecureContext || navigator.share === undefined) {
    console.error('Error: Unsupported feature: navigator.share');

    return;
  }

  var title = this.title;
  var url = this.href;
  navigator.share({ title: title, url: url }).then(() => console.info('Successfully sent share'), (error) => console.error('Error sharing: ' + error));
}

function onLoad() {
  var buttons = document.getElementsByClassName('share_button');
  var shareAvailable = window.isSecureContext && navigator.share !== undefined;

  [].forEach.call(buttons, function (button) {
    if (shareAvailable) {
      button.style.display = 'block';

      [].forEach.call(button.getElementsByTagName('a'), function (a) {
        a.addEventListener('click', doWebShare);
      });
    }
    else {
      button.style.display = 'none';
    }
  });
}

let langData;
function lang() {
  if (!langData) {
    $.ajax({
      url: document.location.protocol + '//static.' + document.querySelector('html').dataset.serverdomain + '/data/i18n/' + document.querySelector('html').lang + '/lang.json',
      dataType: 'json',
      async: false,
      success: function (json) {
        langData = json;
      }
    });
  }

  return langData;
}

let countriesData;
function countries() {
  if (!countriesData) {
    $.ajax({
      url: '/cgi/i18n/countries.pl',
      dataType: 'json',
      async: false,
      success: function (json) {
        countriesData = json;
      }
    });
  }

  return countriesData;
}

window.addEventListener('load', onLoad);

$(function () {
  $("#select_country").select2({
    allowClear: true,
    ajax: {
      url: '/cgi/i18n/countries.pl',
      dataType: 'json',
      processResults: function (data) {
        const results = [];
        // eslint-disable-next-line guard-for-in
        for (var k in data) {
          results.push({ id: k, text: data[k] });
        }

        const locale = document.querySelector('html').lang;

        return {
          results: results.sort(function (a, b) {
            return a.text.localeCompare(b.text, locale);
          })
        };
      }
    }
  }).on("select2:select", function (e) {
    var subdomain = e.params.data.id;
    if (!subdomain) {
      subdomain = 'world';
    }

    window.location.href = document.location.protocol + '//' + subdomain + '.' + document.querySelector('html').dataset.serverdomain;
  }).on("select2:unselect", function () {
    window.location.href = document.location.protocol + '//world.' + document.querySelector('html').dataset.serverdomain;
  });
});
<commit_msg>Country dropdown - Force "World" to the top of the list<commit_after>// This file is part of Product Opener.
//
// Product Opener
// Copyright (C) 2011-2019 Association Open Food Facts
// Contact: contact@openfoodfacts.org
// Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France
//
// Product Opener is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

/* eslint-disable no-undefined */
/*exported lang countries*/

function doWebShare(e) {
  e.preventDefault();

  if (!window.isSecureContext || navigator.share === undefined) {
    console.error('Error: Unsupported feature: navigator.share');

    return;
  }

  var title = this.title;
  var url = this.href;
  navigator.share({ title: title, url: url }).then(() => console.info('Successfully sent share'), (error) => console.error('Error sharing: ' + error));
}

function onLoad() {
  var buttons = document.getElementsByClassName('share_button');
  var shareAvailable = window.isSecureContext && navigator.share !== undefined;

  [].forEach.call(buttons, function (button) {
    if (shareAvailable) {
      button.style.display = 'block';

      [].forEach.call(button.getElementsByTagName('a'), function (a) {
        a.addEventListener('click', doWebShare);
      });
    }
    else {
      button.style.display = 'none';
    }
  });
}

let langData;
function lang() {
  if (!langData) {
    $.ajax({
      url: document.location.protocol + '//static.' + document.querySelector('html').dataset.serverdomain + '/data/i18n/' + document.querySelector('html').lang + '/lang.json',
      dataType: 'json',
      async: false,
      success: function (json) {
        langData = json;
      }
    });
  }

  return langData;
}

let countriesData;
function countries() {
  if (!countriesData) {
    $.ajax({
      url: '/cgi/i18n/countries.pl',
      dataType: 'json',
      async: false,
      success: function (json) {
        countriesData = json;
      }
    });
  }

  return countriesData;
}

window.addEventListener('load', onLoad);

$(function () {
  $("#select_country").select2({
    allowClear: true,
    ajax: {
      url: '/cgi/i18n/countries.pl',
      dataType: 'json',
      processResults: function (data) {
        const results = [];
        var worldresult;
        // eslint-disable-next-line guard-for-in
        for (var k in data) {
          if (k == 'world') {
            worldresult = { id: k, text: data[k] };
          } else {
            results.push({ id: k, text: data[k] });
          }
        }

        const locale = document.querySelector('html').lang;

        results.sort(function (a, b) {
          return a.text.localeCompare(b.text, locale);
        });
        results.unshift(worldresult);

        return {
          results: results
        };
      }
    }
  }).on("select2:select", function (e) {
    var subdomain = e.params.data.id;
    if (!subdomain) {
      subdomain = 'world';
    }

    window.location.href = document.location.protocol + '//' + subdomain + '.' + document.querySelector('html').dataset.serverdomain;
  }).on("select2:unselect", function () {
    window.location.href = document.location.protocol + '//world.' + document.querySelector('html').dataset.serverdomain;
  });
});
<|endoftext|>


<commit_before>/**@license
 *  sysend.js - send messages between browser windows/tabs
 *  Copyright (C) 2014 Jakub Jankiewicz <http://jcubic.pl>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  The idea for this implementation came from this StackOverflow question:
 *  http://stackoverflow.com/q/24182409/387194
 */
var sysend = (function() {
    // we use prefix so `foo' event don't collide with `foo' locaStorage value
    var uniq_prefix = '___sysend___';
    // we use id because storage event is not executed if message was not
    // changed, and we want it if user send same object twice (before it will
    // be removed)
    var id = 0;
    function get(key) {
        return localStorage.getItem(uniq_prefix + key);
    }
    function set(key, value) {
        localStorage.setItem(uniq_prefix + key, value);
    }
    function remove(key) {
        localStorage.removeItem(uniq_prefix + key);
    }
    function to_json(input) {
        var obj = [id++];
        // undefined in array get stringified as null
        if (typeof input != 'undefined') {
            obj.push(input);
        }
        return JSON.stringify(obj);
    }
    function from_json(json) {
        return JSON.parse(json);
    }
    // object with user events as keys and values arrays of callback functions
    var callbacks = {};
    window.addEventListener('storage', function(e) {
        // get user key
        var key = e.key.replace(new RegExp('^' + uniq_prefix), '');
        if (callbacks[key]) {
            var obj = JSON.parse(get(key));
            if (obj) {
                // don't call on remove
                callbacks[key].forEach(function(fn) {
                    fn(obj[1], key);
                });
            }
        }
    }, false);
    var timer;
    return {
        broadcast: function(event, message) {
            set(event, to_json(message));
            clearTimeout(timer);
            // clean up localstorage
            timer = setTimeout(function() {
                remove(event);
            }, 1000);
        },
        on: function(event, fn) {
            if (!callbacks[event]) {
                callbacks[event] = [];
            }
            callbacks[event].push(fn);
        },
        off: function(event, fn) {
            if (callbacks[event]) {
                if (fn) {
                    for (var i=callbacks[event].length; i--;) {
                        if (callbacks[event][i] == fn) {
                            callbacks[event].splice(i, 1);
                        }
                    }
                } else {
                    for (var i=callbacks[event].length; i--;) {
                        callbacks[event].splice(i, 1);
                    }
                }
            }
        }
    };
})();
<commit_msg>remove splice on `off`<commit_after>/**@license
 *  sysend.js - send messages between browser windows/tabs
 *  Copyright (C) 2014 Jakub Jankiewicz <http://jcubic.pl>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  The idea for this implementation came from this StackOverflow question:
 *  http://stackoverflow.com/q/24182409/387194
 */
var sysend = (function() {
    // we use prefix so `foo' event don't collide with `foo' locaStorage value
    var uniq_prefix = '___sysend___';
    // we use id because storage event is not executed if message was not
    // changed, and we want it if user send same object twice (before it will
    // be removed)
    var id = 0;
    function get(key) {
        return localStorage.getItem(uniq_prefix + key);
    }
    function set(key, value) {
        localStorage.setItem(uniq_prefix + key, value);
    }
    function remove(key) {
        localStorage.removeItem(uniq_prefix + key);
    }
    function to_json(input) {
        var obj = [id++];
        // undefined in array get stringified as null
        if (typeof input != 'undefined') {
            obj.push(input);
        }
        return JSON.stringify(obj);
    }
    function from_json(json) {
        return JSON.parse(json);
    }
    // object with user events as keys and values arrays of callback functions
    var callbacks = {};
    window.addEventListener('storage', function(e) {
        // get user key
        var key = e.key.replace(new RegExp('^' + uniq_prefix), '');
        if (callbacks[key]) {
            var obj = JSON.parse(get(key));
            if (obj) {
                // don't call on remove
                callbacks[key].forEach(function(fn) {
                    fn(obj[1], key);
                });
            }
        }
    }, false);
    var timer;
    return {
        broadcast: function(event, message) {
            set(event, to_json(message));
            clearTimeout(timer);
            // clean up localstorage
            timer = setTimeout(function() {
                remove(event);
            }, 1000);
        },
        on: function(event, fn) {
            if (!callbacks[event]) {
                callbacks[event] = [];
            }
            callbacks[event].push(fn);
        },
        off: function(event, fn) {
            if (callbacks[event]) {
                if (fn) {
                    for (var i=callbacks[event].length; i--;) {
                        if (callbacks[event][i] == fn) {
                            callbacks[event].splice(i, 1);
                        }
                    }
                } else {
                    callbacks[event] = [];
                }
            }
        }
    };
})();
<|endoftext|>


<commit_before>import * as NeuralNetwork from "./NeuralNetwork.js";

export { Base };

/**
 * There are many neural networks inside. The apply() feeds the same input to the these different neural networks.
 *
 * @member {string} name
 *   This test filters' name.
 *
 * @member {number[]} totalChannelExpansionFactor
 *   The final output of this neural network will have ( totalChannelExpansionFactor * sourceChannelCount ) channel count.
 *
 * @member {number[]} sourceImageHeightWidth
 *   The size (i.e. [ height, width ]) of the source image. When apply() is called, the source image will be extracted from the sourceCanvas
 * and be resized to this size. The neural network receives this resized source image.
 */
class Base {

  /**
   * @param {number} neuralNetworkCount
   *   Create how many neural networks.
   *
//!!! should use ( strActivationName == "cos" ) and ( bBias == false ) to achieve bias-by-naturally.

   * @param {boolean} bBiasByConstChannel
   *   If true, arrange channel and filter with constant value to achieve bias. This will take more memory (for pre-make filters)
   * but may improve performance (because tf.add() is removed).
   *
   * @param {boolean} bWebWorker
   *   If true, neural network will executed inside a web worker.
   *
   * @see Blocks.init
   */
  init(
    sourceHeight, sourceChannelCount,
    stepCountPerBlock,
    bChannelShuffler,
    pointwise1ChannelCountRate,
    strAvgMaxConv, depthwiseFilterHeight, depthwiseChannelMultiplierBlock0Step0, bBias, strActivationName,
    neuralNetworkCount,
    bWebWorker
  ) {

    this.disposeTensors();

    this.neuralNetworkArray = new Array( neuralNetworkCount );
    for ( let i = 0; i < this.neuralNetworkArray.length; ++i ) {
      let neuralNetwork = new NeuralNetwork.Base();
      neuralNetwork.init(
        sourceHeight, sourceChannelCount,
        stepCountPerBlock,
        bChannelShuffler,
        pointwise1ChannelCountRate,
        strAvgMaxConv, depthwiseFilterHeight, depthwiseChannelMultiplierBlock0Step0, bBias, strActivationName,
        neuralNetworkCount
      );

      this.neuralNetworkArray[ i ] = neuralNetwork;
    }

//!!! ...unfinished...
    this.bWebWorker = bWebWorker;

  }

  disposeTensors() {
    if ( this.neuralNetworkArray ) {
      for ( let i = 0; i < this.neuralNetworkArray.length; ++i ) {
        let neuralNetwork = this.neuralNetworkArray[ i ];
        neuralNetwork.disposeTensors();
      }
      this.neuralNetworkArray = null;
    }
  }

  /**
   * @param {HTMLCanvasElement} sourceCanvas
   *   The canvas which provides image.
   *
   * @param {boolean} bReturn
   *   If true, the result tensors will be returned. Otherwise, the result tensors will be disposed.
   *
   * @return {tf.tensor4d[]}
   *   If ( bReturn == true ), return array of the result tensor. Otheriwse, the result tensors will be disposed and null will be returned.
   */
  apply( sourceCanvas, bReturn ) {

    let sourceImageChannelCount = 4; // tf.browser.fromPixels() handles RGBA 4 channels faster than RGB 3 channels.

//!!! Transferring typed-array is better than ImageData because the ImageData should be re-constructed to typed-array again by another web worker.
//     let ctx = sourceCanvas.getContext( '2d' );
//     let sourceImageData = ctx.getImageData( 0, 0, sourceCanvas.width, sourceCanvas.height );
//
//     // Using fromPixels() to get source image so that we can always dispose all tensors (including sourceImage) except the returning tensor.
//     let sourceImageTensor = tf.browser.fromPixels( sourceImageData, sourceImageChannelCount );
//
//     // Resize source image to a default size (height x width) which is used when training the neural network.
//     let t = tf.image.resizeBilinear( sourceImageTensor, this.sourceImageHeightWidth, true ); // alignCorners = true
//     sourceImageTensor.dispose();

    // Using fromPixels() to get source image so that we can always dispose all tensors (including sourceImage) except the returning tensor.
    let sourceImageTensor = tf.browser.fromPixels( sourceCanvas, sourceImageChannelCount );

    // Resize source image to a default size (height x width) which is used when training the neural network.
    let scaledSourceImageTensor = tf.image.resizeBilinear( sourceImageTensor, this.sourceImageHeightWidth, true ); // alignCorners = true
    sourceImageTensor.dispose();

//!!! ...unfinished...
// here should convert sourceImageData to tensor, get typed-array (so that the receiver worker could convert to tensor again without re-construct typed-array),
// transfer it to a web worker. Then, the web worker do the following fromPixels(), ... etc.
//
//     scaledSourceImageTensor.data().then( ( typedArray ) => {
//       let message = [ values: typedArray, shape: scaledSourceImageTensor.shape, dtype: scaledSourceImageTensor ];
//       worker.postMessage( message, [ message.values.data.buffer ] );
//     });

//!!! the following will dispose the scaledSourceImageTensor before the above codes get typed-array for another web worker.

//???
    let resultArray;
    if ( bReturn )
      resultArray = new Array( this.neuralNetworkArray.length );

    let neuralNetwork;
    for ( let i = 0; i < this.neuralNetworkArray.length; ++i ) {
      neuralNetwork = this.neuralNetworkArray[ i ];
      let t = neuralNetwork.apply_and_destroy_or_keep( scaledSourceImageTensor );

      if ( bReturn )
        resultArray[ i ] = t;
      else
        t.dispose();
    }

    if ( bReturn ) {
      return resultArray;
    } else {
      return null;
    }
  }

}
<commit_msg>Update NeuralNetworks.js<commit_after>import * as NeuralNetwork from "./NeuralNetwork.js";

export { Base };

/**
 * There are many neural networks inside. The apply() feeds the same input to the these different neural networks.
 *
 * @member {string} name
 *   This test filters' name.
 *
 * @member {number[]} totalChannelExpansionFactor
 *   The final output of this neural network will have ( totalChannelExpansionFactor * sourceChannelCount ) channel count.
 *
 * @member {number[]} sourceImageHeightWidth
 *   The size (i.e. [ height, width ]) of the source image. When apply() is called, the source image will be extracted from the sourceCanvas
 * and be resized to this size. The neural network receives this resized source image.
 */
class Base {

  /**
   * @param {number} neuralNetworkCount
   *   Create how many neural networks.
   *
//!!! should use ( strActivationName == "cos" ) and ( bBias == false ) to achieve bias-by-naturally.

   * @param {boolean} bBiasByConstChannel
   *   If true, arrange channel and filter with constant value to achieve bias. This will take more memory (for pre-make filters)
   * but may improve performance (because tf.add() is removed).
   *
   * @param {boolean} bWebWorker
   *   If true, neural network will executed inside a web worker.
   *
   * @see Blocks.init
   */
  init(
    sourceHeight, sourceChannelCount,
    stepCountPerBlock,
    bChannelShuffler,
    pointwise1ChannelCountRate,
    strAvgMaxConv, depthwiseFilterHeight, depthwiseChannelMultiplierBlock0Step0, bBias, strActivationName,
    neuralNetworkCount,
    bWebWorker
  ) {

    this.disposeTensors();

    this.neuralNetworkArray = new Array( neuralNetworkCount );
    for ( let i = 0; i < this.neuralNetworkArray.length; ++i ) {
      let neuralNetwork = new NeuralNetwork.Base();
      neuralNetwork.init(
        sourceHeight, sourceChannelCount,
        stepCountPerBlock,
        bChannelShuffler,
        pointwise1ChannelCountRate,
        strAvgMaxConv, depthwiseFilterHeight, depthwiseChannelMultiplierBlock0Step0, bBias, strActivationName,
        neuralNetworkCount
      );

      this.neuralNetworkArray[ i ] = neuralNetwork;
    }

//!!! ...unfinished...
    this.bWebWorker = bWebWorker;

  }

  disposeTensors() {
    if ( this.neuralNetworkArray ) {
      for ( let i = 0; i < this.neuralNetworkArray.length; ++i ) {
        let neuralNetwork = this.neuralNetworkArray[ i ];
        neuralNetwork.disposeTensors();
      }
      this.neuralNetworkArray = null;
    }
  }

  /**
   * @param {HTMLCanvasElement} sourceCanvas
   *   The canvas which provides image.
   *
   * @param {boolean} bReturn
   *   If true, the result tensors will be returned. Otherwise, the result tensors will be disposed.
   *
   * @return {tf.tensor4d[]}
   *   If ( bReturn == true ), return array of the result tensor. Otheriwse, the result tensors will be disposed and nothing will be returned.
   */
  apply( sourceCanvas, bReturn ) {

    let sourceImageChannelCount = 4; // tf.browser.fromPixels() handles RGBA 4 channels faster than RGB 3 channels.

//!!! Transferring typed-array is better than ImageData because the ImageData should be re-constructed to typed-array again by another web worker.
//     let ctx = sourceCanvas.getContext( '2d' );
//     let sourceImageData = ctx.getImageData( 0, 0, sourceCanvas.width, sourceCanvas.height );
//
//     // Using fromPixels() to get source image so that we can always dispose all tensors (including sourceImage) except the returning tensor.
//     let sourceImageTensor = tf.browser.fromPixels( sourceImageData, sourceImageChannelCount );
//
//     // Resize source image to a default size (height x width) which is used when training the neural network.
//     let t = tf.image.resizeBilinear( sourceImageTensor, this.sourceImageHeightWidth, true ); // alignCorners = true
//     sourceImageTensor.dispose();

    // Using fromPixels() to get source image so that we can always dispose all tensors (including sourceImage) except the returning tensor.
    let sourceImageTensor = tf.browser.fromPixels( sourceCanvas, sourceImageChannelCount );

    // Resize source image to a default size (height x width) which is used when training the neural network.
    let scaledSourceImageTensor = tf.image.resizeBilinear( sourceImageTensor, this.sourceImageHeightWidth, true ); // alignCorners = true
    sourceImageTensor.dispose();

//!!! ...unfinished...
// here should convert sourceImageData to tensor, get typed-array (so that the receiver worker could convert to tensor again without re-construct typed-array),
// transfer it to a web worker. Then, the web worker do the following fromPixels(), ... etc.
//
//     scaledSourceImageTensor.data().then( ( typedArray ) => {
//       let message = [ values: typedArray, shape: scaledSourceImageTensor.shape, dtype: scaledSourceImageTensor ];
//       worker.postMessage( message, [ message.values.data.buffer ] );
//     });

//!!! the following will dispose the scaledSourceImageTensor before the above codes get typed-array for another web worker.

//???
    let resultArray;
    if ( bReturn )
      resultArray = new Array( this.neuralNetworkArray.length );

    let neuralNetwork;
    for ( let i = 0; i < this.neuralNetworkArray.length; ++i ) {
      neuralNetwork = this.neuralNetworkArray[ i ];
      let t = neuralNetwork.apply_and_destroy_or_keep( scaledSourceImageTensor );

      if ( bReturn )
        resultArray[ i ] = t;
      else
        t.dispose();
    }

    if ( bReturn )
      return resultArray;
    else
      return null;
  }

}
<|endoftext|>


<commit_before>var isbnREdelimited = /[^\d](\d{9,12}[\dXx])([^\dXx]|$)/;

function $x(path, context)
{
   if ( !context )
   {
      context = document;
   }
   var result = [];
   var xpr = document.evaluate(path, context, null,
                               XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
   for (var i = 0; item = xpr.snapshotItem(i); i++)
   {
      result.push(item);
   }
   return result;
};

// Figure out which site the source page comes from.
// To add a new one, make a new block like the "chapters"
// and "amazon" variables below, and extend the "if...else..."
// block at the bottom of this function.
function whichSiteIsThis()
{
   var chapters =
      {
         getIsbn: function()
         {
            try
            {
               return location.href.match(isbnREdelimited)[1];
            }
            catch ( e )
            {
               return null;
            }
         },

         getOriginalTitle: function()
         {
            return $x("//h1")[0];
         }
      }

   var allconsuming =
      {
         getIsbn: function()
         {
            var isbn = null;
            isbnLinkNode = $x("//div[@class='item-header-body']/a[@class='amazon-link']/@href")[0];
            if ( isbnLinkNode )
            {
               isbn = isbnLinkNode.firstChild.nodeValue.match(isbnREdelimited)[1];
            }
            return isbn;
         },

         getOriginalTitle: function()
         {
            return $x("//div[@class='item-header-body']/strong")[0];
         }
      }

   var amazon =
      {
         getIsbn: function()
         {
            try
            {
               return location.href.match(isbnREdelimited)[1];
            }
            catch ( e )
            {
               return null;
            }
         },

         getOriginalTitle: function()
         {
            return $x("//div[@class='buying']/b[@class='sans']|//div[@class='buying']/b[@class='asinTitle']|//div[@class='buying']//span[@id='btAsinTitle']")[0];
         }
      }

   var librarything =
      {
         getIsbn: function()
         {
            var isbn = document.body.innerHTML.match(/ISBN:([0-9X]+)/)[1];
            return isbn;
         },

         getOriginalTitle: function()
         {
            return $x("//div[@id='usercover']")[0];
         }
      }

   var powells =
      {
         getIsbn: function()
         {
            try
            {
               return location.href.match(isbnREdelimited)[1];
            }
            catch ( e )
            {
               return null;
            }
         },

         getOriginalTitle: function()
         {
            return $x("//div[@id='seemore']")[0];
         }
      }

   var googleBooks =
      {
         getIsbn: function()
         {
            try
            {
               return location.href.match(/(\d{9,12}[\dXx])/)[1];
            }
            catch ( e )
            {
               return null;
            }
         },

         getOriginalTitle: function()
         {
            return $x("//span[@class='title']")[0];
         }
      }

   var goodReads =
      {
         getIsbn: function()
         {
            try
            {
               var isbnHeaderNodeCandidates = $x("//div[@class='infoBoxRowTitle']");

               for ( var i = 0; i < isbnHeaderNodeCandidates.length; i++ )
               {
                  if ( isbnHeaderNodeCandidates[i].innerHTML.match(/isbn/) )
                  {
                      var isbnNode = isbnHeaderNodeCandidates[i].nextSibling;
                      while ( isbnNode.nodeName != 'DIV' )
                      {
                          isbnNode = isbnNode.nextSibling;
                      }
                      var isbnText = isbnNode.innerHTML.match(isbnREdelimited)[1];
                      return isbnText;
                  }
               }
            }
            catch ( e )
            {
               log.console('error looking for ISBN: ' + e);
            }
            return null;
         },

         getOriginalTitle: function()
         {
            return $x("//h1[@id='bookPageTitle']")[0];
         }
      }

   // figure out what site we're looking at
   if ( location.href.match(/chapters/) )
   {
      return chapters;
   }
   else if ( location.href.match(/allconsuming/) )
   {
      return allconsuming;
   }
   else if ( location.href.match(/powells/) )
   {
      return powells;
   }
   else if ( location.href.match(/google/) )
   {
      return googleBooks;
   }
   else if ( location.href.match(/goodreads/) )
   {
      return goodReads;
   }
   else if ( location.href.match(/librarything/) )
   {
      return librarything;
   }
   else
   {
      // Amazon's pretty popular - make it the default
      return amazon;
   }
}

var knownPage = whichSiteIsThis();
if ( knownPage )
{
   var found_isbn = knownPage.getIsbn();
   if ( found_isbn )
   {
      console.log('requesting: found_isbn = ' + found_isbn);
      chrome.extension.sendRequest(request={msg: 'lookup', isbn: found_isbn});
   }
}
<commit_msg>using delimited ISBN for Google matches<commit_after>var isbnREdelimited = /[^\d](\d{9,12}[\dXx])([^\dXx]|$)/;

function $x(path, context)
{
   if ( !context )
   {
      context = document;
   }
   var result = [];
   var xpr = document.evaluate(path, context, null,
                               XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
   for (var i = 0; item = xpr.snapshotItem(i); i++)
   {
      result.push(item);
   }
   return result;
};

// Figure out which site the source page comes from.
// To add a new one, make a new block like the "chapters"
// and "amazon" variables below, and extend the "if...else..."
// block at the bottom of this function.
function whichSiteIsThis()
{
   var chapters =
      {
         getIsbn: function()
         {
            try
            {
               return location.href.match(isbnREdelimited)[1];
            }
            catch ( e )
            {
               return null;
            }
         },

         getOriginalTitle: function()
         {
            return $x("//h1")[0];
         }
      }

   var allconsuming =
      {
         getIsbn: function()
         {
            var isbn = null;
            isbnLinkNode = $x("//div[@class='item-header-body']/a[@class='amazon-link']/@href")[0];
            if ( isbnLinkNode )
            {
               isbn = isbnLinkNode.firstChild.nodeValue.match(isbnREdelimited)[1];
            }
            return isbn;
         },

         getOriginalTitle: function()
         {
            return $x("//div[@class='item-header-body']/strong")[0];
         }
      }

   var amazon =
      {
         getIsbn: function()
         {
            try
            {
               return location.href.match(isbnREdelimited)[1];
            }
            catch ( e )
            {
               return null;
            }
         },

         getOriginalTitle: function()
         {
            return $x("//div[@class='buying']/b[@class='sans']|//div[@class='buying']/b[@class='asinTitle']|//div[@class='buying']//span[@id='btAsinTitle']")[0];
         }
      }

   var librarything =
      {
         getIsbn: function()
         {
            var isbn = document.body.innerHTML.match(/ISBN:([0-9X]+)/)[1];
            return isbn;
         },

         getOriginalTitle: function()
         {
            return $x("//div[@id='usercover']")[0];
         }
      }

   var powells =
      {
         getIsbn: function()
         {
            try
            {
               return location.href.match(isbnREdelimited)[1];
            }
            catch ( e )
            {
               return null;
            }
         },

         getOriginalTitle: function()
         {
            return $x("//div[@id='seemore']")[0];
         }
      }

   var googleBooks =
      {
         getIsbn: function()
         {
            try
            {
               return location.href.match(isbnREdelimited)[1];
            }
            catch ( e )
            {
               return null;
            }
         },

         getOriginalTitle: function()
         {
            return $x("//span[@class='title']")[0];
         }
      }

   var goodReads =
      {
         getIsbn: function()
         {
            try
            {
               var isbnHeaderNodeCandidates = $x("//div[@class='infoBoxRowTitle']");

               for ( var i = 0; i < isbnHeaderNodeCandidates.length; i++ )
               {
                  if ( isbnHeaderNodeCandidates[i].innerHTML.match(/isbn/) )
                  {
                      var isbnNode = isbnHeaderNodeCandidates[i].nextSibling;
                      while ( isbnNode.nodeName != 'DIV' )
                      {
                          isbnNode = isbnNode.nextSibling;
                      }
                      var isbnText = isbnNode.innerHTML.match(isbnREdelimited)[1];
                      return isbnText;
                  }
               }
            }
            catch ( e )
            {
               log.console('error looking for ISBN: ' + e);
            }
            return null;
         },

         getOriginalTitle: function()
         {
            return $x("//h1[@id='bookPageTitle']")[0];
         }
      }

   // figure out what site we're looking at
   if ( location.href.match(/chapters/) )
   {
      return chapters;
   }
   else if ( location.href.match(/allconsuming/) )
   {
      return allconsuming;
   }
   else if ( location.href.match(/powells/) )
   {
      return powells;
   }
   else if ( location.href.match(/google/) )
   {
      return googleBooks;
   }
   else if ( location.href.match(/goodreads/) )
   {
      return goodReads;
   }
   else if ( location.href.match(/librarything/) )
   {
      return librarything;
   }
   else
   {
      // Amazon's pretty popular - make it the default
      return amazon;
   }
}

var knownPage = whichSiteIsThis();
if ( knownPage )
{
   var found_isbn = knownPage.getIsbn();
   if ( found_isbn )
   {
      console.log('requesting: found_isbn = ' + found_isbn);
      chrome.extension.sendRequest(request={msg: 'lookup', isbn: found_isbn});
   }
}
<|endoftext|>