//# sourceURL=application.js // // application.js // CS50 // // Created by CS50 on 5/3/21. // /* * This file provides an example skeletal stub for the server-side implementation * of a TVML application. * * A javascript file such as this should be provided at the tvBootURL that is * configured in the AppDelegate of the TVML application. Note that the various * javascript functions here are referenced by name in the AppDelegate. This skeletal * implementation shows the basic entry points that you will want to handle * application lifecycle events. */ /** * @description The onLaunch callback is invoked after the application JavaScript * has been parsed into a JavaScript context. The handler is passed an object * that contains options passed in for launch. These options are defined in the * swift or objective-c client code. Options can be used to communicate to * your JavaScript code that data and as well as state information, like if the * the app is being launched in the background. * * The location attribute is automatically added to the object and represents * the URL that was used to retrieve the application JavaScript. */ var baseURL; var jsonMainFile; var indexPage; function toXML(string) { return new DOMParser().parseFromString(string, "application/xml"); } function requestJSON(url, successCallback) { let request = new XMLHttpRequest(); request.responseType = "json"; request.open("GET", url, true); request.onload = function() { successCallback(request.response); } request.send(); } /** * This convenience function returns an alert template, which can be used to present errors to the user. */ function showAlert(title, description) { let alertString = ` ${title} ${description} ` let parser = new DOMParser(); let alertDoc = parser.parseFromString(alertString, "application/xml"); navigationDocument.presentModal(alertDoc) } function getDocument(extension, restore=false) { let url = baseURL + extension; let templateXHR = new XMLHttpRequest(); templateXHR.responseType = "document"; templateXHR.addEventListener("error", (error) => { console.log(error) if (error.target["status"] === 404) { const lastViewed = localStorage.getItem("lastViewed") if (extension === lastViewed) { localStorage.removeItem("lastViewed"); getDocument("/templates/index.xml"); } else { showAlert("Errored loading Page", `${extension} not found\n Please quit and restart the app.`) } } else { showAlert("Something's Wrong", "Please quit and restart the app. If the problem persists, please contact sysadmins@cs50.harvard.edu.") } }); templateXHR.addEventListener("load", () => { pushPage(templateXHR.responseXML); localStorage.setItem("lastViewed", extension); if (restore) { insertIndexPage() } }, false); if (!restore) { pushLoadingDocument("Loading..."); } templateXHR.open("GET", url, true); templateXHR.send(); } function fetchDocument(extension) { let url = baseURL + extension; let templateXHR = new XMLHttpRequest(); templateXHR.responseType = "document"; templateXHR.addEventListener("error", (error) => { console.log(error); }); templateXHR.addEventListener("load", () => { return templateXHR.responseXML; }, false); templateXHR.open("GET", url, true); templateXHR.send(); } function insertIndexPage() { let url = baseURL + "/templates/index.xml"; let templateXHR = new XMLHttpRequest(); templateXHR.responseType = "document"; templateXHR.addEventListener("load", function() { templateXHR.responseXML.addEventListener("load", (ev) => { }) navigationDocument.insertBeforeDocument(templateXHR.responseXML, getActiveDocument()) }, false); templateXHR.open("GET", url, true); templateXHR.send() } /** Presents a loading screen for the user when loading contents from the remote server. */ function pushLoadingDocument(title) { navigationDocument.pushDocument(loadingDocument("Loading...")); } function loadingDocument(title) { let loadingMarkup = ` ${title} `; return toXML(loadingMarkup); } function pushPage(document) { let currentDoc = getActiveDocument(); try { if (currentDoc.getElementsByTagName("loadingTemplate").item(0) == null) { navigationDocument.pushDocument(document) } else { navigationDocument.replaceDocument(document, currentDoc) } } catch (error) { navigationDocument.pushDocument(document) } } function updateCourseInfo(identifier) { const nickname = jsonMainFile[identifier]["nickname"] const title = jsonMainFile[identifier]["title"]; const description = jsonMainFile[identifier]["description"]; let currentDoc = getActiveDocument(); currentDoc.getElementById("course-nickname").innerHTML = nickname; currentDoc.getElementById("course-title").innerHTML = title; currentDoc.getElementById("course-description").innerHTML = description; currentDoc.getElementById("course-bg").setAttribute("src", `"${jsonMainFile[identifier]["heroImg"]}"`); } function expandDescription(event) { const alert = ` ${event.innerHTML} ` navigationDocument.pushDocument(toXML(alert)); } function updatePodcastInfo(year, episode) { let currentDoc = getActiveDocument() currentDoc.getElementById("current-heroImg").setAttribute("src", jsonMainFile["podcast"]["year"][year][episode]["artworkImageURL"]) currentDoc.getElementById("current-description").innerHTML = jsonMainFile["podcast"]["year"][year][episode]["description"] } function playMedia(identifier, year, number, seekToSeconds=undefined) { // Instantiate media player let player = new Player(); // Construct single media item const mediaItem = jsonMainFile[identifier]["year"][year][number]; let singleMediaItem = new MediaItem(mediaItem["type"], mediaItem["url"]); singleMediaItem.title = mediaItem["title"] // Configure Media Item info if (mediaItem["type"] === "audio") { let overlayDoc = ` ${mediaItem['title']} ${mediaItem['description']} ` player.addEventListener("transportBarVisibilityDidChange", function(e) { if (e.hidden) { player.interactiveOverlayDocument = toXML(overlayDoc); } }); } else { singleMediaItem.description = mediaItem["description"]; singleMediaItem.artworkImageURL = mediaItem["artworkImageURL"]; } // Update playback state for current media item player.addEventListener("stateDidChange", (state) => { if (!isNaN(state["elapsedTime"])) { // Keep track of playbackState for current media localStorage.setItem(`playbackState_${mediaItem["url"]}`, JSON.stringify(state)); } }); let mediaList = new Playlist(); mediaList.push(singleMediaItem); player.playlist = mediaList; if (seekToSeconds != undefined) { player.seekToTime(seekToSeconds); player.play() navigationDocument.dismissModal(); return; } // Retrieve playback state for current media item, if any resumePlaybackCondition: if (localStorage.getItem(`playbackState_${mediaItem["url"]}`) != undefined) { let playbackState = JSON.parse(localStorage.getItem(`playbackState_${mediaItem["url"]}`)); // If playhead info is valid, determine if media should play from the beginning if (playbackState["elapsedTime"] == null) { localStorage.removeItem(`playbackState_${mediaItem["url"]}`); break resumePlaybackCondition; } else { const currentProgress = playbackState["elapsedTime"] / playbackState["duration"] if (playbackState["elapsedTime"] < 10 || currentProgress > 0.99) { player.play(); return } } let resumePrompt = ` ${mediaItem["title"]} Do you want to resume playback at ${new Date(playbackState["elapsedTime"] * 1000).toISOString().substring(11, 19)}? ` navigationDocument.presentModal(toXML(resumePrompt)); return } player.play(); } function getMainJSONFile() { // Always requests the latest JSON file from CDN const jsonURL = baseURL + `/json/main.json`; requestJSON(jsonURL, (jsonText) => { jsonMainFile = jsonText; }); } function menuBarItemPresenter() { getActiveDocument().addEventListener("select", (event) => { const target = event.target; const ele = target.parentNode; const feature = ele.getFeature("MenuBarDocument"); const featureDoc = feature.getDocument(target); if (featureDoc == undefined) { feature.setDocument(loadingDocument("Loading..."), target); const url = baseURL + target.getAttribute("documentURL"); let templateXHR = new XMLHttpRequest(); templateXHR.responseType = "document"; templateXHR.addEventListener("load", function() { feature.setDocument(templateXHR.responseXML, target); }, false); templateXHR.open("GET", url, true); templateXHR.send() } }); } App.onLaunch = function(options) { // Set global base URL baseURL = options.BASEURL; // Set index page path indexPage = "/templates/index.xml" // Always requests the latest JSON file from CDN const jsonURL = baseURL + `/json/main.json`; requestJSON(jsonURL, (jsonText) => { jsonMainFile = jsonText; const lastViewed = localStorage.getItem("lastViewed") try { if (lastViewed !== undefined && lastViewed !== indexPage) { getDocument(lastViewed, true) } else { getDocument(indexPage) } } catch (error) { console.log(error) } }); } App.onWillResignActive = function() { try { // If the last viewed page is the index page, remove it from localStorage if (getActiveDocument().getElementsByTagName("mainTemplate").length != 0) { localStorage.removeItem("lastViewed") } } catch (error) {} } App.onDidEnterBackground = function() { // Not used. } App.onWillEnterForeground = function() { getMainJSONFile() }