//# 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()
}