<commit_before>var lingcod = (function(){

    var options = {};
    var that = {};
    var layers = [];

    var constructor_defaults = {
        hideGoogleLayers: false
    };

    that.addLayer = function(url, opts){
        layers.push({url: url, opts: opts});
    };

    that.init = function(opts){
        options = jQuery.extend({}, constructor_defaults, opts);
        that.options = opts;

        $('#sidebar').tabs({
            select: function(event, ui){
                if($(this).hasClass('masked')){
                    event.preventDefault();
                }
            }
        });

        var selectedTab = getStore('selectedTab');
        if(selectedTab){
            $('#sidebar').tabs('select', selectedTab);
        };

        resize();

        $('.marinemap-panel').live('click', function(){
            if(!$(this).hasClass('marinemap-menu-items')){
                lingcod.menu_items.closeAll();
            }
        });

        if(window.google){
            google.earth.createInstance(
                "map",
                function(i){
                    geInit(i);
                },
                function(code){
                    geFailure(code);
                });
            setupListeners();
            lingcod.menu_items.init($('.menu_items'));
            // makes button clicking a lot more reliable!!
        }else{
            geFailure();
        }
        // just so that buttons act more like buttons
        $(document).find('#meta-navigation a, a.button, a.close, ' +
            '.menu_items span, .ui-tabs-nav a').live('dragstart', function(){
            return false;
        });
    };

    var geInit = function(pluginInstance){
        ge = pluginInstance;
        ge.getWindow().setVisibility(true); // required
        ge.getOptions().setStatusBarVisibility(true);
        gex = new GEarthExtensions(ge);

        that.geocoder = new lingcod.map.geocoder(gex, $('#flyToLocation'));
        that.measureTool = new lingcod.measureTool();

        $('#measure_distance').click(function(){
            that.measureTool.clear();
            that.measureTool.measureDistance(gex, "measureAmount");
            $('#measure_clear').removeClass('disabled');
            $('#measureAmountHolder').show();
        });
        $('#measure_area').click(function(){
            that.measureTool.clear();
            that.measureTool.measureArea(gex, "measureAmount");
            $('#measure_clear').removeClass('disabled');
            $('#measureAmountHolder').show();
        });
        $('#measure_clear').click(function(){
            that.measureTool.clear();
            $(this).addClass('disabled');
            $('#measureAmountHolder').hide();
        });
        $('#measure_units').change(function(){
            that.measureTool.setUnits($(this).val());
        });

        var setEarthOptions = function(){
            $('#earthOptions li').each(function(){
                var li = $(this);
                switch(li.attr('id')){
                    case 'nav':
                        if(li.hasClass('visible')){
                            ge.getNavigationControl().setVisibility(
                                ge.VISIBILITY_AUTO);
                        }else{
                            ge.getNavigationControl().setVisibility(
                                ge.VISIBILITY_HIDE);
                        }
                        break;
                    case 'overview':
                        ge.getOptions().setOverviewMapVisibility(
                            li.hasClass('visible'));
                        break;
                    case 'scale':
                        ge.getOptions().setScaleLegendVisibility(
                            li.hasClass('visible'));
                        break;
                    case 'atm':
                        ge.getOptions().setAtmosphereVisibility(
                            li.hasClass('visible'));
                        break;
                    case 'terrain':
                        ge.getLayerRoot().enableLayerById(
                            ge.LAYER_TERRAIN, li.hasClass('visible'));
                        break;
                }
            });
        }

        $('#earthOptions li').click(function(e){
            $(this).toggleClass('visible');
            setEarthOptions();
        });

        setEarthOptionsFromLocalStore();
        setEarthOptions();

        var cameraSet = setCameraFromLocalStorage();

        for(var i=0; i<layers.length; i++){
            var div = $('

');
            $('#datalayerstree').append(div);
            layers[i].tree = kmltree({
                url: layers[i].url,
                ge: ge,
                gex: gex,
                map_div: $('#map'),
                element: div,
                restoreState: !$.browser.msie,
                setExtent: !cameraSet && layers[i].opts &&
                    layers[i].opts.setExtent
            });
            layers[i].tree.load();
        }

        if(!that.options.hideGoogleLayers){
            var div = $('

');
            $('#datalayerstree').append(div);

            var googleLayers = kmltree({
                url: options.media_url + 'common/fixtures/earthLayers.kml',
                ge: ge,
                gex: gex,
                map_div: $('#map'),
                element: div,
                restoreState: true,
                supportItemIcon: true
            });

            var updateGoogleLayers = function(tree){
                $('#googlelayers li').each(function(){
                    var item = $(this);
                    var name = item.find('span.name').text();
                    switch(name){
                        case 'Grid':
                            ge.getOptions().setGridVisibility(
                                item.hasClass('visible'));
                            break;
                        case '3d Buildings':
                            ge.getLayerRoot().enableLayerById(
                                ge.LAYER_BUILDINGS, item.hasClass('visible'));
                            break;
                        case 'Low Resolution 3d Buildings':
                            ge.getLayerRoot().enableLayerById(
                                ge.LAYER_BUILDINGS_LOW_RESOLUTION,
                                item.hasClass('visible'));
                            break;
                        case 'Roads':
                            ge.getLayerRoot().enableLayerById(
                                ge.LAYER_ROADS, item.hasClass('visible'));
                            break;
                        case 'Borders and Labels':
                            ge.getLayerRoot().enableLayerById(
                                ge.LAYER_BORDERS, item.hasClass('visible'));
                            break;
                    }
                });
            }

            $(googleLayers).bind('kmlLoaded', function(){
                updateGoogleLayers(googleLayers);
            });

            $(googleLayers).bind('toggleItem', function(){
                updateGoogleLayers(googleLayers);
            });

            googleLayers.load();
        }

        var panel = lingcod.panel({appendTo: $('#panel-holder'),
            showCloseButton: false});

        that.client = lingcod.rest.client(gex, panel);

        // Allows projects to add a callback to run after any form is shown
        if(typeof options.form_shown === 'function'){
            $(that.client).bind('form_shown', options.form_shown);
        }

        var editors = [];

        if(options.myshapes){
            for(var i=0;i<options.myshapes.length; i++){
                var url = options.myshapes[i].url;
                var callback = options.myshapes[i]['callback'];
                var editor = lingcod.rest.kmlEditor({
                    ge: ge,
                    gex: gex,
                    appendTo: '#myshapestree',
                    div: '#map',
                    url: options.myshapes[i].url,
                    client: that.client,
                    shared: false,
                    allow_copy: options.allow_copy
                });
                if(callback){
                    $(editor).bind('kmlLoaded', function(event, e, kmlObject){
                        callback(this, this.el, kmlObject)
                    });
                }
                editors.push(editor);
                $(editor.tree).bind('copyDone', function(e,location) {
                    var myshapesEditor = editors[0];
                    myshapesEditor.tree.clearSelection();
                    myshapesEditor.refresh( function() {
                        var node = myshapesEditor.tree.getNodesById(location);
                        if(node.length === 1) {
                            myshapesEditor.tree.selectNode(node);
                        }
                    });
                });
            }
        }

        $('a.myshapes-link').live('click', function(e){
            e.preventDefault();
            $('.marinemap-panel:visible a.close').click();
            var href = $(this).attr('href');
            for(var i=0;i<editors.length;i++){
                var editor = editors[i];
                var nodes = editor.tree.getNodesById(href);
                if(nodes.length === 1){
                    editor.tree.selectNode(nodes);
                    nodes.trigger('dblclick');
                    return false;
                }
            }
            return false;
        });

        that.editors = editors;

        if(options.sharedshapes){
            for(var i=0;i<options.sharedshapes.length; i++){
                var editor = lingcod.rest.kmlEditor({
                    ge: ge,
                    gex: gex,
                    appendTo: '#sharedshapestree',
                    div: '#map',
                    url: options.sharedshapes[i],
                    client: that.client,
                    shared: true,
                    allow_copy: options.allow_copy
                });
                $(editor.tree).bind('copyDone', function(e, location) {
                    $('#sidebar').tabs('select', "#MyShapes");
                    var myshapesEditor = that.editors[0];
                    myshapesEditor.tree.clearSelection();
                    myshapesEditor.refresh( function() {
                        var node = myshapesEditor.tree.getNodesById(location);
                        if(node.length === 1) {
                            myshapesEditor.tree.selectNode(node);
                        }
                    });
                });
                editors.push(editor);
            }
        }

        var onEditorSelect = function(e, originalEvent, node, kmlObject){
            if(node){
                if(node.parents('#MyShapes').length !== 0){
                    $('#sidebar').tabs('select', '#MyShapes');
                }else{
                    $('#sidebar').tabs('select', '#SharedShapes');
                }
            }
        };

        for(var i=0;i<editors.length;i++){
            $(editors[i]).bind('select', onEditorSelect);
        };

        $('#sidebar, #meta-navigation').click(function(e){
            if(e.target === this || e.target === $('#MyShapes')[0]){
                for(var i=0;i<editors.length;i++){
                    editors[i].clearSelection();
                }
            }
        });

        // If news or about links aren't included in the interface these will
        // do nothing (good for extensible templates).
        $('#news').click(function(e){
            opts = {};
            opts['load_msg'] = 'Loading News';
            opts['showClose'] = true;
            panel.showUrl(that.options.news_url, opts);
            e.preventDefault();
        });

        $('#about').click(function(e){
            opts = {};
            opts['load_msg'] = 'Loading Intro';
            opts['showClose'] = true;
            panel.showUrl(that.options.about_url, opts);
            e.preventDefault();
        });

        // for showing the news or about panels if they haven't been viewed
        // yet (that determination is done with cookies in the django view)
        if(options.show_panel){
            opts = {};
            opts['showClose'] = true;
            if(options.show_panel == 'about' && that.options.about_url){
                panel.showUrl(that.options.about_url, opts);
            }else if(options.show_panel == 'news' && that.options.news_url){
                panel.showUrl(that.options.news_url, opts);
            }
        }

        // A UI enhancement to add a drop-shadow from sidebar falling on map
        var url = that.options.media_url + 'common/kml/shadow.kmz';
        google.earth.fetchKml(ge, url, function(k){
            ge.getFeatures().appendChild(k);
        });

        $('#sidebar-toggler').click(function(){
            $('#map_container').css({
                'width': '100%',
                'left': 0,
                'z-index': 10
            });
            $('.menu_items').hide();
            $('#sidebar > .ui-tabs-nav').hide();
            $(this).hide();
            $('#show-sidebar').show();
        });

        $('#show-sidebar').click(function(){
            $(this).hide();
            $('#map_container').css({left: 500});
            $('.menu_items').show();
            $('#sidebar > .ui-tabs-nav').show();
            $('#sidebar-toggler').show();
            resize();
        });

        $(window).bind('beforeunload', function(){
            setCameraToLocalStorage();
            saveEarthOptionsToLocalStore();
            setStore('selectedTab',
                $('#sidebar > ul > .ui-tabs-selected a').attr('href'));
        });
    };

    var studyRegionLoaded = function(kmlObject, node){
        // Reorder so studyRegion is on top of the list
        $('#datalayerstree').prepend(node);
        if (kmlObject.getAbstractView()){
            ge.getView().setAbstractView(kmlObject.getAbstractView());
        }
    };

    var setStore = function(key, value){
        if(!!window.localStorage){
            localStorage.setItem(key, value);
        }
    };

    var getStore = function(key){
        if(!!window.localStorage){
            return localStorage.getItem(key);
        }else{
            return false;
        }
    }

    var setEarthOptionsFromLocalStore = function(){
        if(!!window.localStorage && localStorage.getItem('earthOptions')){
            var json = JSON.parse(localStorage.getItem('earthOptions'));
            for(key in json){
                $('#earthOptions').find('#'+key).toggleClass('visible',
                    json[key]);
            }
        }else{
            return false;
        }
    };

    var saveEarthOptionsToLocalStore = function(){
        if(!!window.localStorage){
            var json = {};
            $('#earthOptions li').each(function(){
                json[$(this).attr('id')] = $(this).hasClass('visible');
            });
            localStorage.setItem('earthOptions', JSON.stringify(json));
        }
    };

    var geFailure = function(errorCode){
        // alert("Failure loading the Google Earth Plugin: " + errorCode);
    }

    var setupListeners = function(){
        $(window).smartresize(function(){
            resize();
        });
        $('#meta-navigation').click(function(){
            lingcod.menu_items.closeAll();
            $('#panel-holder').find('a.close:visible').click();
        });
        $('#sidebar').bind('mouseup', function(e){
            lingcod.menu_items.closeAll();
            // return false;
        });
        $('#sidebar-mask').click(function(){
            lingcod.menu_items.closeAll();
        });
        $('#sidebar > .ui-tabs-nav li a').click(function(){
            $('#panel-holder').find('a.close:visible').click();
        });

        $('#panel-holder').click(function(e){
            if(e.originalTarget === this){
                lingcod.menu_items.closeAll();
                $('#panel-holder').find('a.close:visible').click();
            }
        });
    };

    var resize = function(){
        var mh = $('#meta-navigation').outerHeight();
        var h = $(document.body).height() - mh;
        $('#sidebar').css({top: mh, height: h});
        $('#panel-holder').css({top: mh, height: h});

        var w = $(document.body).width() - $('#sidebar').width();
        $('#map_container').height(h).width(w);
    };

    that.resize = resize;

    that.maskSidebar = function(){
        $('#panel-holder').show();
        $('#sidebar').addClass('masked');
    };

    that.unmaskSidebar = function(){
        if($('#panel-holder').find('.marinemap-panel:visible').length === 0){
            $('#panel-holder').hide();
            $('#sidebar').removeClass('masked');
        }
    };

    that.showLoadingMask = function(msg){
        var msg = msg || 'Loading';
        var lmsg = $('#sidebar-mask').find('span.loadingMsg');
        lmsg.text(msg);
        lmsg.show();
        lingcod.menu_items.disable();
        $('#sidebar').addClass('masked');
    };

    that.hideLoadingMask = function(){
        $('#sidebar-mask').find('span.loadingMsg').hide();
        lingcod.menu_items.enable();
        that.unmaskSidebar();
    }

    var panels = [];

    that.addPanel = function(panel){
        panels.push(panel);
        $(panel).bind('panelshow', onPanelShown);
        $(panel).bind('panelhide', onPanelHide);
        $(panel).bind('panelclose', onPanelHide);
    };

    var onPanelShown = function(e, panel){
        that.maskSidebar();
    };

    var onPanelHide = function(e, panel){
        var count = 0;
        for(var i=0; i<panels.length; i++){
            var p = panels[i];
            if(p.shown){
                count++;
            }
        }
        if(count <= 0){
            that.unmaskSidebar();
        }
    };

    var setCameraToLocalStorage = function(){
        if(!!window.localStorage){
            localStorage.setItem('marinemap-camera', gex.view.serialize());
        }
    };

    var setCameraFromLocalStorage = function(){
        if(!!window.localStorage && localStorage.getItem('marinemap-camera')){
            gex.view.deserialize(localStorage.getItem('marinemap-camera'));
            return true;
        }
        return false;
    };

    that.persistentReports = {};

    return that;
})();<commit_msg>Added event to lingcod - earthReady - so that projects can assign callbacks when the google earth plugin has been initialized<commit_after>var lingcod = (function(){

    var options = {};
    var that = {};
    var layers = [];

    var constructor_defaults = {
        hideGoogleLayers: false
    };

    that.addLayer = function(url, opts){
        layers.push({url: url, opts: opts});
    };

    that.init = function(opts){
        options = jQuery.extend({}, constructor_defaults, opts);
        that.options = opts;

        $('#sidebar').tabs({
            select: function(event, ui){
                if($(this).hasClass('masked')){
                    event.preventDefault();
                }
            }
        });

        var selectedTab = getStore('selectedTab');
        if(selectedTab){
            $('#sidebar').tabs('select', selectedTab);
        };

        resize();

        $('.marinemap-panel').live('click', function(){
            if(!$(this).hasClass('marinemap-menu-items')){
                lingcod.menu_items.closeAll();
            }
        });

        if(window.google){
            google.earth.createInstance(
                "map",
                function(i){
                    geInit(i);
                },
                function(code){
                    geFailure(code);
                });
            setupListeners();
            lingcod.menu_items.init($('.menu_items'));
            // makes button clicking a lot more reliable!!
        }else{
            geFailure();
        }
        // just so that buttons act more like buttons
        $(document).find('#meta-navigation a, a.button, a.close, ' +
            '.menu_items span, .ui-tabs-nav a').live('dragstart', function(){
            return false;
        });
    };

    var geInit = function(pluginInstance){
        ge = pluginInstance;
        ge.getWindow().setVisibility(true); // required
        ge.getOptions().setStatusBarVisibility(true);
        gex = new GEarthExtensions(ge);

        that.geocoder = new lingcod.map.geocoder(gex, $('#flyToLocation'));
        that.measureTool = new lingcod.measureTool();

        $('#measure_distance').click(function(){
            that.measureTool.clear();
            that.measureTool.measureDistance(gex, "measureAmount");
            $('#measure_clear').removeClass('disabled');
            $('#measureAmountHolder').show();
        });
        $('#measure_area').click(function(){
            that.measureTool.clear();
            that.measureTool.measureArea(gex, "measureAmount");
            $('#measure_clear').removeClass('disabled');
            $('#measureAmountHolder').show();
        });
        $('#measure_clear').click(function(){
            that.measureTool.clear();
            $(this).addClass('disabled');
            $('#measureAmountHolder').hide();
        });
        $('#measure_units').change(function(){
            that.measureTool.setUnits($(this).val());
        });

        var setEarthOptions = function(){
            $('#earthOptions li').each(function(){
                var li = $(this);
                switch(li.attr('id')){
                    case 'nav':
                        if(li.hasClass('visible')){
                            ge.getNavigationControl().setVisibility(
                                ge.VISIBILITY_AUTO);
                        }else{
                            ge.getNavigationControl().setVisibility(
                                ge.VISIBILITY_HIDE);
                        }
                        break;
                    case 'overview':
                        ge.getOptions().setOverviewMapVisibility(
                            li.hasClass('visible'));
                        break;
                    case 'scale':
                        ge.getOptions().setScaleLegendVisibility(
                            li.hasClass('visible'));
                        break;
                    case 'atm':
                        ge.getOptions().setAtmosphereVisibility(
                            li.hasClass('visible'));
                        break;
                    case 'terrain':
                        ge.getLayerRoot().enableLayerById(
                            ge.LAYER_TERRAIN, li.hasClass('visible'));
                        break;
                }
            });
        }

        $('#earthOptions li').click(function(e){
            $(this).toggleClass('visible');
            setEarthOptions();
        });

        setEarthOptionsFromLocalStore();
        setEarthOptions();

        var cameraSet = setCameraFromLocalStorage();

        for(var i=0; i<layers.length; i++){
            var div = $('

');
            $('#datalayerstree').append(div);
            layers[i].tree = kmltree({
                url: layers[i].url,
                ge: ge,
                gex: gex,
                map_div: $('#map'),
                element: div,
                restoreState: !$.browser.msie,
                setExtent: !cameraSet && layers[i].opts &&
                    layers[i].opts.setExtent
            });
            layers[i].tree.load();
        }

        if(!that.options.hideGoogleLayers){
            var div = $('

');
            $('#datalayerstree').append(div);

            var googleLayers = kmltree({
                url: options.media_url + 'common/fixtures/earthLayers.kml',
                ge: ge,
                gex: gex,
                map_div: $('#map'),
                element: div,
                restoreState: true,
                supportItemIcon: true
            });

            var updateGoogleLayers = function(tree){
                $('#googlelayers li').each(function(){
                    var item = $(this);
                    var name = item.find('span.name').text();
                    switch(name){
                        case 'Grid':
                            ge.getOptions().setGridVisibility(
                                item.hasClass('visible'));
                            break;
                        case '3d Buildings':
                            ge.getLayerRoot().enableLayerById(
                                ge.LAYER_BUILDINGS, item.hasClass('visible'));
                            break;
                        case 'Low Resolution 3d Buildings':
                            ge.getLayerRoot().enableLayerById(
                                ge.LAYER_BUILDINGS_LOW_RESOLUTION,
                                item.hasClass('visible'));
                            break;
                        case 'Roads':
                            ge.getLayerRoot().enableLayerById(
                                ge.LAYER_ROADS, item.hasClass('visible'));
                            break;
                        case 'Borders and Labels':
                            ge.getLayerRoot().enableLayerById(
                                ge.LAYER_BORDERS, item.hasClass('visible'));
                            break;
                    }
                });
            }

            $(googleLayers).bind('kmlLoaded', function(){
                updateGoogleLayers(googleLayers);
            });

            $(googleLayers).bind('toggleItem', function(){
                updateGoogleLayers(googleLayers);
            });

            googleLayers.load();

            $(that).trigger('earthReady', [ge, gex]);
        }

        var panel = lingcod.panel({appendTo: $('#panel-holder'),
            showCloseButton: false});

        that.client = lingcod.rest.client(gex, panel);

        // Allows projects to add a callback to run after any form is shown
        if(typeof options.form_shown === 'function'){
            $(that.client).bind('form_shown', options.form_shown);
        }

        var editors = [];

        if(options.myshapes){
            for(var i=0;i<options.myshapes.length; i++){
                var url = options.myshapes[i].url;
                var callback = options.myshapes[i]['callback'];
                var editor = lingcod.rest.kmlEditor({
                    ge: ge,
                    gex: gex,
                    appendTo: '#myshapestree',
                    div: '#map',
                    url: options.myshapes[i].url,
                    client: that.client,
                    shared: false,
                    allow_copy: options.allow_copy
                });
                if(callback){
                    $(editor).bind('kmlLoaded', function(event, e, kmlObject){
                        callback(this, this.el, kmlObject)
                    });
                }
                editors.push(editor);
                $(editor.tree).bind('copyDone', function(e,location) {
                    var myshapesEditor = editors[0];
                    myshapesEditor.tree.clearSelection();
                    myshapesEditor.refresh( function() {
                        var node = myshapesEditor.tree.getNodesById(location);
                        if(node.length === 1) {
                            myshapesEditor.tree.selectNode(node);
                        }
                    });
                });
            }
        }

        $('a.myshapes-link').live('click', function(e){
            e.preventDefault();
            $('.marinemap-panel:visible a.close').click();
            var href = $(this).attr('href');
            for(var i=0;i<editors.length;i++){
                var editor = editors[i];
                var nodes = editor.tree.getNodesById(href);
                if(nodes.length === 1){
                    editor.tree.selectNode(nodes);
                    nodes.trigger('dblclick');
                    return false;
                }
            }
            return false;
        });

        that.editors = editors;

        if(options.sharedshapes){
            for(var i=0;i<options.sharedshapes.length; i++){
                var editor = lingcod.rest.kmlEditor({
                    ge: ge,
                    gex: gex,
                    appendTo: '#sharedshapestree',
                    div: '#map',
                    url: options.sharedshapes[i],
                    client: that.client,
                    shared: true,
                    allow_copy: options.allow_copy
                });
                $(editor.tree).bind('copyDone', function(e, location) {
                    $('#sidebar').tabs('select', "#MyShapes");
                    var myshapesEditor = that.editors[0];
                    myshapesEditor.tree.clearSelection();
                    myshapesEditor.refresh( function() {
                        var node = myshapesEditor.tree.getNodesById(location);
                        if(node.length === 1) {
                            myshapesEditor.tree.selectNode(node);
                        }
                    });
                });
                editors.push(editor);
            }
        }

        var onEditorSelect = function(e, originalEvent, node, kmlObject){
            if(node){
                if(node.parents('#MyShapes').length !== 0){
                    $('#sidebar').tabs('select', '#MyShapes');
                }else{
                    $('#sidebar').tabs('select', '#SharedShapes');
                }
            }
        };

        for(var i=0;i<editors.length;i++){
            $(editors[i]).bind('select', onEditorSelect);
        };

        $('#sidebar, #meta-navigation').click(function(e){
            if(e.target === this || e.target === $('#MyShapes')[0]){
                for(var i=0;i<editors.length;i++){
                    editors[i].clearSelection();
                }
            }
        });

        // If news or about links aren't included in the interface these will
        // do nothing (good for extensible templates).
        $('#news').click(function(e){
            opts = {};
            opts['load_msg'] = 'Loading News';
            opts['showClose'] = true;
            panel.showUrl(that.options.news_url, opts);
            e.preventDefault();
        });

        $('#about').click(function(e){
            opts = {};
            opts['load_msg'] = 'Loading Intro';
            opts['showClose'] = true;
            panel.showUrl(that.options.about_url, opts);
            e.preventDefault();
        });

        // for showing the news or about panels if they haven't been viewed
        // yet (that determination is done with cookies in the django view)
        if(options.show_panel){
            opts = {};
            opts['showClose'] = true;
            if(options.show_panel == 'about' && that.options.about_url){
                panel.showUrl(that.options.about_url, opts);
            }else if(options.show_panel == 'news' && that.options.news_url){
                panel.showUrl(that.options.news_url, opts);
            }
        }

        // A UI enhancement to add a drop-shadow from sidebar falling on map
        var url = that.options.media_url + 'common/kml/shadow.kmz';
        google.earth.fetchKml(ge, url, function(k){
            ge.getFeatures().appendChild(k);
        });

        $('#sidebar-toggler').click(function(){
            $('#map_container').css({
                'width': '100%',
                'left': 0,
                'z-index': 10
            });
            $('.menu_items').hide();
            $('#sidebar > .ui-tabs-nav').hide();
            $(this).hide();
            $('#show-sidebar').show();
        });

        $('#show-sidebar').click(function(){
            $(this).hide();
            $('#map_container').css({left: 500});
            $('.menu_items').show();
            $('#sidebar > .ui-tabs-nav').show();
            $('#sidebar-toggler').show();
            resize();
        });

        $(window).bind('beforeunload', function(){
            setCameraToLocalStorage();
            saveEarthOptionsToLocalStore();
            setStore('selectedTab',
                $('#sidebar > ul > .ui-tabs-selected a').attr('href'));
        });
    };

    var studyRegionLoaded = function(kmlObject, node){
        // Reorder so studyRegion is on top of the list
        $('#datalayerstree').prepend(node);
        if (kmlObject.getAbstractView()){
            ge.getView().setAbstractView(kmlObject.getAbstractView());
        }
    };

    var setStore = function(key, value){
        if(!!window.localStorage){
            localStorage.setItem(key, value);
        }
    };

    var getStore = function(key){
        if(!!window.localStorage){
            return localStorage.getItem(key);
        }else{
            return false;
        }
    }

    var setEarthOptionsFromLocalStore = function(){
        if(!!window.localStorage && localStorage.getItem('earthOptions')){
            var json = JSON.parse(localStorage.getItem('earthOptions'));
            for(key in json){
                $('#earthOptions').find('#'+key).toggleClass('visible',
                    json[key]);
            }
        }else{
            return false;
        }
    };

    var saveEarthOptionsToLocalStore = function(){
        if(!!window.localStorage){
            var json = {};
            $('#earthOptions li').each(function(){
                json[$(this).attr('id')] = $(this).hasClass('visible');
            });
            localStorage.setItem('earthOptions', JSON.stringify(json));
        }
    };

    var geFailure = function(errorCode){
        // alert("Failure loading the Google Earth Plugin: " + errorCode);
    }

    var setupListeners = function(){
        $(window).smartresize(function(){
            resize();
        });
        $('#meta-navigation').click(function(){
            lingcod.menu_items.closeAll();
            $('#panel-holder').find('a.close:visible').click();
        });
        $('#sidebar').bind('mouseup', function(e){
            lingcod.menu_items.closeAll();
            // return false;
        });
        $('#sidebar-mask').click(function(){
            lingcod.menu_items.closeAll();
        });
        $('#sidebar > .ui-tabs-nav li a').click(function(){
            $('#panel-holder').find('a.close:visible').click();
        });

        $('#panel-holder').click(function(e){
            if(e.originalTarget === this){
                lingcod.menu_items.closeAll();
                $('#panel-holder').find('a.close:visible').click();
            }
        });
    };

    var resize = function(){
        var mh = $('#meta-navigation').outerHeight();
        var h = $(document.body).height() - mh;
        $('#sidebar').css({top: mh, height: h});
        $('#panel-holder').css({top: mh, height: h});

        var w = $(document.body).width() - $('#sidebar').width();
        $('#map_container').height(h).width(w);
    };

    that.resize = resize;

    that.maskSidebar = function(){
        $('#panel-holder').show();
        $('#sidebar').addClass('masked');
    };

    that.unmaskSidebar = function(){
        if($('#panel-holder').find('.marinemap-panel:visible').length === 0){
            $('#panel-holder').hide();
            $('#sidebar').removeClass('masked');
        }
    };

    that.showLoadingMask = function(msg){
        var msg = msg || 'Loading';
        var lmsg = $('#sidebar-mask').find('span.loadingMsg');
        lmsg.text(msg);
        lmsg.show();
        lingcod.menu_items.disable();
        $('#sidebar').addClass('masked');
    };

    that.hideLoadingMask = function(){
        $('#sidebar-mask').find('span.loadingMsg').hide();
        lingcod.menu_items.enable();
        that.unmaskSidebar();
    }

    var panels = [];

    that.addPanel = function(panel){
        panels.push(panel);
        $(panel).bind('panelshow', onPanelShown);
        $(panel).bind('panelhide', onPanelHide);
        $(panel).bind('panelclose', onPanelHide);
    };

    var onPanelShown = function(e, panel){
        that.maskSidebar();
    };

    var onPanelHide = function(e, panel){
        var count = 0;
        for(var i=0; i<panels.length; i++){
            var p = panels[i];
            if(p.shown){
                count++;
            }
        }
        if(count <= 0){
            that.unmaskSidebar();
        }
    };

    var setCameraToLocalStorage = function(){
        if(!!window.localStorage){
            localStorage.setItem('marinemap-camera', gex.view.serialize());
        }
    };

    var setCameraFromLocalStorage = function(){
        if(!!window.localStorage && localStorage.getItem('marinemap-camera')){
            gex.view.deserialize(localStorage.getItem('marinemap-camera'));
            return true;
        }
        return false;
    };

    that.persistentReports = {};

    return that;
})();<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

goog.provide('shaka.polyfill.MediaSource');

goog.require('shaka.log');
goog.require('shaka.polyfill');
goog.require('shaka.util.MimeUtils');
goog.require('shaka.util.Platform');

/**
 * @summary A polyfill to patch MSE bugs.
 */
shaka.polyfill.MediaSource = class {
  /**
   * Install the polyfill if needed.
   */
  static install() {
    shaka.log.debug('MediaSource.install');

    // MediaSource bugs are difficult to detect without checking for the
    // affected platform.  SourceBuffer is not always exposed on window, for
    // example, and instances are only accessible after setting up MediaSource
    // on a video element.  Because of this, we use UA detection and other
    // platform detection tricks to decide which patches to install.

    if (!window.MediaSource) {
      shaka.log.info('No MSE implementation available.');
    } else if (window.cast && cast.__platform__ &&
               cast.__platform__.canDisplayType) {
      shaka.log.info('Patching Chromecast MSE bugs.');
      // Chromecast cannot make accurate determinations via isTypeSupported.
      shaka.polyfill.MediaSource.patchCastIsTypeSupported_();
    } else if (shaka.util.Platform.isApple()) {
      const version = navigator.appVersion;

      // TS content is broken on Safari in general.
      // See https://github.com/google/shaka-player/issues/743
      // and https://bugs.webkit.org/show_bug.cgi?id=165342
      shaka.polyfill.MediaSource.rejectTsContent_();

      if (version.includes('Version/8')) {
        // Safari 8 does not implement appendWindowEnd.  If we ignore the
        // incomplete MSE implementation, some content (especially multi-period)
        // will fail to play correctly.  The best we can do is blacklist Safari
        // 8.
        shaka.log.info('Blacklisting Safari 8 MSE.');
        shaka.polyfill.MediaSource.blacklist_();
      } else if (version.includes('Version/9')) {
        shaka.log.info('Patching Safari 9 MSE bugs.');
        // Safari 9 does not correctly implement abort() on SourceBuffer.
        // Calling abort() causes a decoder failure, rather than resetting the
        // decode timestamp as called for by the spec.
        // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=160316
        shaka.polyfill.MediaSource.stubAbort_();
      } else if (version.includes('Version/10')) {
        shaka.log.info('Patching Safari 10 MSE bugs.');
        // Safari 10 does not correctly implement abort() on SourceBuffer.
        // Calling abort() before appending a segment causes that segment to be
        // incomplete in buffer.
        // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
        shaka.polyfill.MediaSource.stubAbort_();
        // Safari 10 fires spurious 'updateend' events after endOfStream().
        // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165336
        shaka.polyfill.MediaSource.patchEndOfStreamEvents_();
      } else if (version.includes('Version/11') ||
                 version.includes('Version/12')) {
        shaka.log.info('Patching Safari 11/12 MSE bugs.');
        // Safari 11 does not correctly implement abort() on SourceBuffer.
        // Calling abort() before appending a segment causes that segment to be
        // incomplete in the buffer.
        // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
        shaka.polyfill.MediaSource.stubAbort_();
        // If you remove up to a keyframe, Safari 11 incorrectly will also
        // remove that keyframe and the content up to the next.
        // Offsetting the end of the removal range seems to help.
        // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=177884
        shaka.polyfill.MediaSource.patchRemovalRange_();
      }
    } else if (shaka.util.Platform.isTizen()) {
      // Tizen's implementation of MSE does not work well with opus. To prevent
      // the player from trying to play opus on Tizen, we will override media
      // source to always reject opus content.

      shaka.polyfill.MediaSource.rejectCodec_('opus');
    } else {
      shaka.log.info('Using native MSE as-is.');
    }
  }

  /**
   * Blacklist the current browser by removing media source. A side-effect of
   * this will be to make |shaka.util.Platform.supportsMediaSource| return
   * |false|.
   *
   * @private
   */
  static blacklist_() {
    window['MediaSource'] = null;
  }

  /**
   * Stub out abort().  On some buggy MSE implementations, calling abort()
   * causes various problems.
   *
   * @private
   */
  static stubAbort_() {
    /* eslint-disable no-restricted-syntax */
    const addSourceBuffer = MediaSource.prototype.addSourceBuffer;
    MediaSource.prototype.addSourceBuffer = function(...varArgs) {
      const sourceBuffer = addSourceBuffer.apply(this, varArgs);
      sourceBuffer.abort = function() {}; // Stub out for buggy implementations.
      return sourceBuffer;
    };
    /* eslint-enable no-restricted-syntax */
  }

  /**
   * Patch remove().  On Safari 11, if you call remove() to remove the content
   * up to a keyframe, Safari will also remove the keyframe and all of the data
   * up to the next one. For example, if the keyframes are at 0s, 5s, and 10s,
   * and you tried to remove 0s-5s, it would instead remove 0s-10s.
   *
   * Offsetting the end of the range seems to be a usable workaround.
   *
   * @private
   */
  static patchRemovalRange_() {
    // eslint-disable-next-line no-restricted-syntax
    const originalRemove = SourceBuffer.prototype.remove;

    // eslint-disable-next-line no-restricted-syntax
    SourceBuffer.prototype.remove = function(startTime, endTime) {
      // eslint-disable-next-line no-restricted-syntax
      return originalRemove.call(this, startTime, endTime - 0.001);
    };
  }

  /**
   * Patch endOfStream() to get rid of 'updateend' events that should not fire.
   * These extra events confuse MediaSourceEngine, which relies on correct
   * events to manage SourceBuffer state.
   *
   * @private
   */
  static patchEndOfStreamEvents_() {
    // eslint-disable-next-line no-restricted-syntax
    const endOfStream = MediaSource.prototype.endOfStream;
    // eslint-disable-next-line no-restricted-syntax
    MediaSource.prototype.endOfStream = function(...varArgs) {
      // This bug manifests only when endOfStream() results in the truncation
      // of the MediaSource's duration.  So first we must calculate what the
      // new duration will be.
      let newDuration = 0;
      for (const buffer of this.sourceBuffers || []) {
        const bufferEnd = buffer.buffered.end(buffer.buffered.length - 1);
        newDuration = Math.max(newDuration, bufferEnd);
      }

      // If the duration is going to be reduced, suppress the next 'updateend'
      // event on each SourceBuffer.
      if (!isNaN(this.duration) &&
          newDuration < this.duration) {
        this.ignoreUpdateEnd_ = true;
        for (const buffer of this.sourceBuffers || []) {
          buffer.eventSuppressed_ = false;
        }
      }

      // eslint-disable-next-line no-restricted-syntax
      return endOfStream.apply(this, varArgs);
    };

    let cleanUpHandlerInstalled = false;
    // eslint-disable-next-line no-restricted-syntax
    const addSourceBuffer = MediaSource.prototype.addSourceBuffer;
    // eslint-disable-next-line no-restricted-syntax
    MediaSource.prototype.addSourceBuffer = function(...varArgs) {
      // After adding a new source buffer, add an event listener to allow us to
      // suppress events.
      // eslint-disable-next-line no-restricted-syntax
      const sourceBuffer = addSourceBuffer.apply(this, varArgs);
      sourceBuffer['mediaSource_'] = this;
      sourceBuffer.addEventListener('updateend',
          shaka.polyfill.MediaSource.ignoreUpdateEnd_, false);

      if (!cleanUpHandlerInstalled) {
        // If we haven't already, install an event listener to allow us to clean
        // up listeners when MediaSource is torn down.
        this.addEventListener('sourceclose',
            shaka.polyfill.MediaSource.cleanUpListeners_, false);
        cleanUpHandlerInstalled = true;
      }
      return sourceBuffer;
    };
  }

  /**
   * An event listener for 'updateend' which selectively suppresses the events.
   *
   * @see shaka.polyfill.MediaSource.patchEndOfStreamEvents_
   *
   * @param {Event} event
   * @private
   */
  static ignoreUpdateEnd_(event) {
    const sourceBuffer = event.target;
    const mediaSource = sourceBuffer['mediaSource_'];

    if (mediaSource.ignoreUpdateEnd_) {
      event.preventDefault();
      event.stopPropagation();
      event.stopImmediatePropagation();
      sourceBuffer.eventSuppressed_ = true;

      for (const buffer of mediaSource.sourceBuffers) {
        if (buffer.eventSuppressed_ == false) {
          // More events need to be suppressed.
          return;
        }
      }

      // All events have been suppressed, all buffers are out of 'updating'
      // mode.  Stop suppressing events.
      mediaSource.ignoreUpdateEnd_ = false;
    }
  }

  /**
   * An event listener for 'sourceclose' which cleans up listeners for
   * 'updateend' to avoid memory leaks.
   *
   * @see shaka.polyfill.MediaSource.patchEndOfStreamEvents_
   * @see shaka.polyfill.MediaSource.ignoreUpdateEnd_
   *
   * @param {Event} event
   * @private
   */
  static cleanUpListeners_(event) {
    const mediaSource = /** @type {!MediaSource} */ (event.target);
    for (const buffer of mediaSource.sourceBuffers || []) {
      buffer.removeEventListener('updateend',
          shaka.polyfill.MediaSource.ignoreUpdateEnd_, false);
    }
    mediaSource.removeEventListener('sourceclose',
        shaka.polyfill.MediaSource.cleanUpListeners_, false);
  }

  /**
   * Patch isTypeSupported() to reject TS content.  Used to avoid TS-related MSE
   * bugs on Safari.
   *
   * @private
   */
  static rejectTsContent_() {
    const originalIsTypeSupported = MediaSource.isTypeSupported;

    MediaSource.isTypeSupported = (mimeType) => {
      // Parse the basic MIME type from its parameters.
      const pieces = mimeType.split(/ *; */);
      const basicMimeType = pieces[0];
      const container = basicMimeType.split('/')[1];

      if (container.toLowerCase() == 'mp2t') {
        return false;
      }

      return originalIsTypeSupported(mimeType);
    };
  }

  /**
   * Patch |MediaSource.isTypeSupported| to always reject |codec|. This is used
   * when we know that we are on a platform that does not work well with a given
   * codec.
   *
   * @param {string} codec
   * @private
   */
  static rejectCodec_(codec) {
    const isTypeSupported = MediaSource.isTypeSupported;

    MediaSource.isTypeSupported = (mimeType) => {
      const actualCodec = shaka.util.MimeUtils.getCodecBase(mimeType);
      return actualCodec != codec && isTypeSupported(mimeType);
    };
  }

  /**
   * Patch isTypeSupported() to parse for HDR-related clues and chain to a
   * private API on the Chromecast which can query for support.
   *
   * @private
   */
  static patchCastIsTypeSupported_() {
    const originalIsTypeSupported = MediaSource.isTypeSupported;

    // Docs from Dolby detailing profile names: https://bit.ly/2T2wKbu
    const dolbyVisionRegex = /^dv(?:h[e1]|a[v1])\./;

    MediaSource.isTypeSupported = (mimeType) => {
      // Parse the basic MIME type from its parameters.
      const pieces = mimeType.split(/ *; */);
      const basicMimeType = pieces.shift();  // Remove pieces[0].

      // Parse the parameters into key-value pairs.
      /** @type {Object.<string, string>} */
      const parameters = {};
      for (const piece of pieces) {
        const kv = piece.split('=');
        const k = kv[0];
        const v = kv[1].replace(/"(.*)"/, '$1');
        parameters[k] = v;
      }

      const codecs = parameters['codecs'];
      if (!codecs) {
        return originalIsTypeSupported(mimeType);
      }

      let isHDR = false;
      let isDolbyVision = false;

      const codecList = codecs.split(',').filter((codec) => {
        if (dolbyVisionRegex.test(codec)) {
          isDolbyVision = true;
        }

        // We take this string as a signal for HDR, but don't remove it.
        // This regex matches the strings "hev1.2" and "hvc1.2".
        if (/^(hev|hvc)1\.2/.test(codec)) {
          isHDR = true;
        }

        // Keep all other strings in the list.
        return true;
      });

      // If the content uses Dolby Vision, we take this as a sign that the
      // content is not HDR after all.
      if (isDolbyVision) {
        isHDR = false;
      }

      // Reconstruct the "codecs" parameter from the results of the filter.
      parameters['codecs'] = codecList.join(',');

      // If the content is HDR, we add this additional parameter so that the
      // Cast platform will check for HDR support.
      if (isHDR) {
        parameters['eotf'] = 'smpte2084';
      }

      // Reconstruct the MIME type, possibly with additional parameters.
      let extendedMimeType = basicMimeType;
      for (const k in parameters) {
        const v = parameters[k];
        extendedMimeType += '; ' + k + '="' + v + '"';
      }
      return cast.__platform__.canDisplayType(extendedMimeType);
    };
  }
};

shaka.polyfill.register(shaka.polyfill.MediaSource.install);
<commit_msg>Update MediaSource polyfills for Safari 13<commit_after>/**
 * @license
 * Copyright 2016 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

goog.provide('shaka.polyfill.MediaSource');

goog.require('shaka.log');
goog.require('shaka.polyfill');
goog.require('shaka.util.MimeUtils');
goog.require('shaka.util.Platform');

/**
 * @summary A polyfill to patch MSE bugs.
 */
shaka.polyfill.MediaSource = class {
  /**
   * Install the polyfill if needed.
   */
  static install() {
    shaka.log.debug('MediaSource.install');

    // MediaSource bugs are difficult to detect without checking for the
    // affected platform.  SourceBuffer is not always exposed on window, for
    // example, and instances are only accessible after setting up MediaSource
    // on a video element.  Because of this, we use UA detection and other
    // platform detection tricks to decide which patches to install.

    if (!window.MediaSource) {
      shaka.log.info('No MSE implementation available.');
    } else if (window.cast && cast.__platform__ &&
               cast.__platform__.canDisplayType) {
      shaka.log.info('Patching Chromecast MSE bugs.');
      // Chromecast cannot make accurate determinations via isTypeSupported.
      shaka.polyfill.MediaSource.patchCastIsTypeSupported_();
    } else if (shaka.util.Platform.isApple()) {
      const match = navigator.appVersion.match(/Version\/(\d+)/);
      const version = parseInt(match[1], /* base */ 10);

      // TS content is broken on Safari in general.
      // See https://github.com/google/shaka-player/issues/743
      // and https://bugs.webkit.org/show_bug.cgi?id=165342
      shaka.polyfill.MediaSource.rejectTsContent_();

      if (version <= 10) {
        // Safari 8 does not implement appendWindowEnd.
        // Safari 9 & 10 do not correctly implement abort() on SourceBuffer.
        // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=160316
        // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
        // Safari 10 fires spurious 'updateend' events after endOfStream().
        // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165336

        // Blacklist these very outdated versions.
        shaka.log.info('Blacklisting MSE on Safari <= 10.');
        shaka.polyfill.MediaSource.blacklist_();
      } else if (version <= 12) {
        shaka.log.info('Patching Safari 11 & 12 MSE bugs.');
        // Safari 11 & 12 do not correctly implement abort() on SourceBuffer.
        // Calling abort() before appending a segment causes that segment to be
        // incomplete in the buffer.
        // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
        shaka.polyfill.MediaSource.stubAbort_();

        // If you remove up to a keyframe, Safari 11 & 12 incorrectly will also
        // remove that keyframe and the content up to the next.
        // Offsetting the end of the removal range seems to help.
        // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=177884
        shaka.polyfill.MediaSource.patchRemovalRange_();
      } else {
        shaka.log.info('Patching Safari 13 MSE bugs.');
        // Safari 13 does not correctly implement abort() on SourceBuffer.
        // Calling abort() before appending a segment causes that segment to be
        // incomplete in the buffer.
        // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
        shaka.polyfill.MediaSource.stubAbort_();
      }
    } else if (shaka.util.Platform.isTizen()) {
      // Tizen's implementation of MSE does not work well with opus. To prevent
      // the player from trying to play opus on Tizen, we will override media
      // source to always reject opus content.

      shaka.polyfill.MediaSource.rejectCodec_('opus');
    } else {
      shaka.log.info('Using native MSE as-is.');
    }
  }

  /**
   * Blacklist the current browser by removing media source. A side-effect of
   * this will be to make |shaka.util.Platform.supportsMediaSource| return
   * |false|.
   *
   * @private
   */
  static blacklist_() {
    window['MediaSource'] = null;
  }

  /**
   * Stub out abort().  On some buggy MSE implementations, calling abort()
   * causes various problems.
   *
   * @private
   */
  static stubAbort_() {
    /* eslint-disable no-restricted-syntax */
    const addSourceBuffer = MediaSource.prototype.addSourceBuffer;
    MediaSource.prototype.addSourceBuffer = function(...varArgs) {
      const sourceBuffer = addSourceBuffer.apply(this, varArgs);
      sourceBuffer.abort = function() {}; // Stub out for buggy implementations.
      return sourceBuffer;
    };
    /* eslint-enable no-restricted-syntax */
  }

  /**
   * Patch remove().  On Safari 11, if you call remove() to remove the content
   * up to a keyframe, Safari will also remove the keyframe and all of the data
   * up to the next one. For example, if the keyframes are at 0s, 5s, and 10s,
   * and you tried to remove 0s-5s, it would instead remove 0s-10s.
   *
   * Offsetting the end of the range seems to be a usable workaround.
   *
   * @private
   */
  static patchRemovalRange_() {
    // eslint-disable-next-line no-restricted-syntax
    const originalRemove = SourceBuffer.prototype.remove;

    // eslint-disable-next-line no-restricted-syntax
    SourceBuffer.prototype.remove = function(startTime, endTime) {
      // eslint-disable-next-line no-restricted-syntax
      return originalRemove.call(this, startTime, endTime - 0.001);
    };
  }

  /**
   * Patch isTypeSupported() to reject TS content.  Used to avoid TS-related MSE
   * bugs on Safari.
   *
   * @private
   */
  static rejectTsContent_() {
    const originalIsTypeSupported = MediaSource.isTypeSupported;

    MediaSource.isTypeSupported = (mimeType) => {
      // Parse the basic MIME type from its parameters.
      const pieces = mimeType.split(/ *; */);
      const basicMimeType = pieces[0];
      const container = basicMimeType.split('/')[1];

      if (container.toLowerCase() == 'mp2t') {
        return false;
      }

      return originalIsTypeSupported(mimeType);
    };
  }

  /**
   * Patch |MediaSource.isTypeSupported| to always reject |codec|. This is used
   * when we know that we are on a platform that does not work well with a given
   * codec.
   *
   * @param {string} codec
   * @private
   */
  static rejectCodec_(codec) {
    const isTypeSupported = MediaSource.isTypeSupported;

    MediaSource.isTypeSupported = (mimeType) => {
      const actualCodec = shaka.util.MimeUtils.getCodecBase(mimeType);
      return actualCodec != codec && isTypeSupported(mimeType);
    };
  }

  /**
   * Patch isTypeSupported() to parse for HDR-related clues and chain to a
   * private API on the Chromecast which can query for support.
   *
   * @private
   */
  static patchCastIsTypeSupported_() {
    const originalIsTypeSupported = MediaSource.isTypeSupported;

    // Docs from Dolby detailing profile names: https://bit.ly/2T2wKbu
    const dolbyVisionRegex = /^dv(?:h[e1]|a[v1])\./;

    MediaSource.isTypeSupported = (mimeType) => {
      // Parse the basic MIME type from its parameters.
      const pieces = mimeType.split(/ *; */);
      const basicMimeType = pieces.shift();  // Remove pieces[0].

      // Parse the parameters into key-value pairs.
      /** @type {Object.<string, string>} */
      const parameters = {};
      for (const piece of pieces) {
        const kv = piece.split('=');
        const k = kv[0];
        const v = kv[1].replace(/"(.*)"/, '$1');
        parameters[k] = v;
      }

      const codecs = parameters['codecs'];
      if (!codecs) {
        return originalIsTypeSupported(mimeType);
      }

      let isHDR = false;
      let isDolbyVision = false;

      const codecList = codecs.split(',').filter((codec) => {
        if (dolbyVisionRegex.test(codec)) {
          isDolbyVision = true;
        }

        // We take this string as a signal for HDR, but don't remove it.
        // This regex matches the strings "hev1.2" and "hvc1.2".
        if (/^(hev|hvc)1\.2/.test(codec)) {
          isHDR = true;
        }

        // Keep all other strings in the list.
        return true;
      });

      // If the content uses Dolby Vision, we take this as a sign that the
      // content is not HDR after all.
      if (isDolbyVision) {
        isHDR = false;
      }

      // Reconstruct the "codecs" parameter from the results of the filter.
      parameters['codecs'] = codecList.join(',');

      // If the content is HDR, we add this additional parameter so that the
      // Cast platform will check for HDR support.
      if (isHDR) {
        parameters['eotf'] = 'smpte2084';
      }

      // Reconstruct the MIME type, possibly with additional parameters.
      let extendedMimeType = basicMimeType;
      for (const k in parameters) {
        const v = parameters[k];
        extendedMimeType += '; ' + k + '="' + v + '"';
      }
      return cast.__platform__.canDisplayType(extendedMimeType);
    };
  }
};

shaka.polyfill.register(shaka.polyfill.MediaSource.install);
<|endoftext|>


<commit_before>'use strict';

var fs      = require('fs');
var path    = require('path');
var Promise = require('bluebird');
var temp    = require('temp');
var editor  = require('editor');

module.exports = prMessage;
function prMessage (template, tpTicketId) {
  temp.track();

  return readMessageTemplate(template)
  .then(interpolateTargeProcessId(tpTicketId))
  .then(writeTemporaryFile)
  .then(launchEditor)
  .then(readPullRequestMessage);
}

function readMessageTemplate (template) {
  var templatePath = path.resolve(template);

  return Promise.promisify(fs.readFile)(templatePath)
  .then( function (data) {
    return data.toString();
  });
}

function interpolateTargeProcessId (targetProcessId) {
  return function (message) {
    var messageWithTargetProcessId = message.replace(/:tp-ticket-id:/g, targetProcessId);

    return Promise.resolve(messageWithTargetProcessId);
  };
}

function writeTemporaryFile (data) {
  return Promise.promisify(temp.open)(null)
  .then( function (info) {
    return Promise.promisify(fs.writeFile)(info.path, data)
    .then( function () {
      return info.path;
    });
  });
}

function launchEditor (path) {
  return new Promise(function (resolve) {
    editor(path, function (code) {
      if (code === 0) {
        resolve(path);
      }
    });
  });
}

function readPullRequestMessage (path) {
  return Promise.promisify(fs.readFile)(path)
  .then( function (data) {
    var message = data.toString();
    var lines   = message.split('\n');
    var title   = lines[0];
    var body    = lines.slice(1, lines.length - 1).join('\n');

    return {title: title, body: body};
  });
}
<commit_msg>Create pull requests messages even without templates<commit_after>'use strict';

var fs      = require('fs');
var path    = require('path');
var Promise = require('bluebird');
var temp    = require('temp');
var editor  = require('editor');

module.exports = prMessage;
function prMessage (template, tpTicketId) {
  temp.track();

  return buildMessageForEditor(template, tpTicketId)
  .then(writeTemporaryFile)
  .then(launchEditor)
  .then(readPullRequestMessage);
}

function buildMessageForEditor (template, tpTicketId) {
  if (template) {
    return readMessageTemplate(template)
    .then(interpolateTargeProcessId(tpTicketId));
  } else {
    return Promise.resolve('');
  }
}

function readMessageTemplate (template) {
  var templatePath = path.resolve(template);

  return Promise.promisify(fs.readFile)(templatePath)
  .then( function (data) {
    return data.toString();
  });
}

function interpolateTargeProcessId (targetProcessId) {
  return function (message) {
    var messageWithTargetProcessId = message.replace(/:tp-ticket-id:/g, targetProcessId);

    return Promise.resolve(messageWithTargetProcessId);
  };
}

function writeTemporaryFile (data) {
  return Promise.promisify(temp.open)(null)
  .then( function (info) {
    return Promise.promisify(fs.writeFile)(info.path, data)
    .then( function () {
      return info.path;
    });
  });
}

function launchEditor (path) {
  return new Promise(function (resolve) {
    editor(path, function (code) {
      if (code === 0) {
        resolve(path);
      }
    });
  });
}

function readPullRequestMessage (path) {
  return Promise.promisify(fs.readFile)(path)
  .then( function (data) {
    var message = data.toString();
    var lines   = message.split('\n');
    var title   = lines[0];
    var body    = lines.slice(1, lines.length - 1).join('\n');

    return {title: title, body: body};
  });
}
<|endoftext|>


<commit_before>import access from './annotations/access';
import alias from './annotations/alias';
import author from './annotations/author';
import content from './annotations/content';
import deprecated from './annotations/deprecated';
import example from './annotations/example';
import group from './annotations/group';
import ignore from './annotations/ignore';
import link from './annotations/link';
import output from './annotations/output';
import parameter from './annotations/parameter';
import property from './annotations/property';
import require from './annotations/require';
import return_ from './annotations/return';
import see from './annotations/see';
import since from './annotations/since';
import throw_ from './annotations/throw';
import todo from './annotations/todo';
import type from './annotations/type';

export default class AnnotationsApi {
  constructor() {
    this.list = {
      _ : { alias: {} }
    };

    this.addAnnotation('access', access);
    this.addAnnotation('alias', alias);
    this.addAnnotation('author', author);
    this.addAnnotation('content', content);
    this.addAnnotation('deprecated', deprecated);
    this.addAnnotation('example', example);
    this.addAnnotation('group', group);
    this.addAnnotation('ignore', ignore);
    this.addAnnotation('link', link);
    this.addAnnotation('output', output);
    this.addAnnotation('parameter', parameter);
    this.addAnnotation('property', property);
    this.addAnnotation('require', require);
    this.addAnnotation('return_', return_);
    this.addAnnotation('see', see);
    this.addAnnotation('since', since);
    this.addAnnotation('throw_', throw_);
    this.addAnnotation('todo', todo);
    this.addAnnotation('type', type);
  }

  /**
   * Add a single annotation by name
   * @param {String} name - Name of the annotation
   * @param {Object} annotation - Annotation object
   */
  addAnnotation(name, annotation) {
    this.list._.alias[name] = name;

    if (Array.isArray(annotation.alias)) {
      annotation.alias.forEach(aliasName => {
        this.list._.alias[aliasName] = name;
      });
    }

    this.list[name] = annotation;
  }

  /**
   * Add an array of annotations. The name of the annoations must be
   * in the `name` key of the annoation.
   * @param {Array} annotations - Annotation objects
   */
  addAnnotations(annotations) {
    if (annotations !== undefined) {
      annotations.forEach(annotation => {
        this.addAnnotation(annotation.name, annotation);
      });
    }
  }
}
<commit_msg>Fix overriding `require`<commit_after>import access from './annotations/access';
import alias from './annotations/alias';
import author from './annotations/author';
import content from './annotations/content';
import deprecated from './annotations/deprecated';
import example from './annotations/example';
import group from './annotations/group';
import ignore from './annotations/ignore';
import link from './annotations/link';
import output from './annotations/output';
import parameter from './annotations/parameter';
import property from './annotations/property';
import require_ from './annotations/require';
import return_ from './annotations/return';
import see from './annotations/see';
import since from './annotations/since';
import throw_ from './annotations/throw';
import todo from './annotations/todo';
import type from './annotations/type';

export default class AnnotationsApi {
  constructor() {
    this.list = {
      _ : { alias: {} }
    };

    this.addAnnotation('access', access);
    this.addAnnotation('alias', alias);
    this.addAnnotation('author', author);
    this.addAnnotation('content', content);
    this.addAnnotation('deprecated', deprecated);
    this.addAnnotation('example', example);
    this.addAnnotation('group', group);
    this.addAnnotation('ignore', ignore);
    this.addAnnotation('link', link);
    this.addAnnotation('output', output);
    this.addAnnotation('parameter', parameter);
    this.addAnnotation('property', property);
    this.addAnnotation('require', require_);
    this.addAnnotation('return_', return_);
    this.addAnnotation('see', see);
    this.addAnnotation('since', since);
    this.addAnnotation('throw_', throw_);
    this.addAnnotation('todo', todo);
    this.addAnnotation('type', type);
  }

  /**
   * Add a single annotation by name
   * @param {String} name - Name of the annotation
   * @param {Object} annotation - Annotation object
   */
  addAnnotation(name, annotation) {
    this.list._.alias[name] = name;

    if (Array.isArray(annotation.alias)) {
      annotation.alias.forEach(aliasName => {
        this.list._.alias[aliasName] = name;
      });
    }

    this.list[name] = annotation;
  }

  /**
   * Add an array of annotations. The name of the annoations must be
   * in the `name` key of the annoation.
   * @param {Array} annotations - Annotation objects
   */
  addAnnotations(annotations) {
    if (annotations !== undefined) {
      annotations.forEach(annotation => {
        this.addAnnotation(annotation.name, annotation);
      });
    }
  }
}
<|endoftext|>