Ö1 – Simple <audio> Stream and Download

Sendungen auf Ö1 schnell und einfach anhören und herunterladen

目前為 2015-10-12 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name        Ö1 – Simple <audio> Stream and Download
// @description Sendungen auf Ö1 schnell und einfach anhören und herunterladen
// @namespace   https://xmine127.tk/gm/
// @include     *://oe1.orf.at/*
// @include     *://loopstream01.apa.at/*#DOWNLOAD=*
// @version     1.1.0
// @grant       unsafeWindow
// @run-at      document-start
// ==/UserScript==

function $A(collection) {
	return Array.prototype.slice.call(collection, 0);
};



function create_download_link(attributes) {
	var DOMDownloadLink = document.createElement("a");
	for(var attribute in attributes) {
		if(attributes.hasOwnProperty(attribute)) {
			DOMDownloadLink[attribute] = attributes[attribute];
		}
	}
	DOMDownloadLink.className = "userscript-audio-download";
	DOMDownloadLink.appendChild(document.createTextNode("Download"));
	return DOMDownloadLink;
}


if(window.location.hash.startsWith("#DOWNLOAD=")) {
	// Download link iframe page
	
	var downloadAttributes = JSON.parse(decodeURIComponent(window.location.hash.substr(10)));
	
	document.addEventListener("DOMContentLoaded", function()
	{
		// Inject style information
		inject_content();
		
		// Mark this page's content as not-important
		document.documentElement.className = "download-proxy";
		
		// Add the (important) download link
		document.body.appendChild(create_download_link(downloadAttributes));
	}, true);
	
	return;
}


document.addEventListener("DOMContentLoaded", function()
{
	// Inject extra page nodes and styles
	inject_content();
	
	// Detect if page URL inherently points to some stream
	var pageTrackID = window.location.pathname.match(/^\/programm\/(\d+).*$/);
	if(pageTrackID instanceof Array) {
		pageTrackID = parseInt(pageTrackID[1]);
	}
	
	// Define replacement function for stream window launcher
	var konsole_orig = unsafeWindow.konsole;
	var konsole = exportFunction(function(url, trackRestore) {
		var DOMPlayButton;
		var trackID;
		if(pageTrackID) {
			// Find play area on program pages
			DOMPlayButton = document.querySelector(".galleryitem > .overlay-7tage");
			
			// Assume that the page's inherent track should be played
			// (although parsing the requested URL would work too)
			trackID = pageTrackID;
		} else {
			// Guess the play button used to play the stream based on the received URL
			DOMPlayButton = document.querySelector(".has-7tage > a[href='" + url.replace("'", "\\'") + "']");
			
			// Determine track ID from URL requested by the caller
			var URLQuery = url.split("#")[0].split("?")[1];
			if(URLQuery) {
				var URLOptions = URLQuery.split("&");
				for(var i = 0; i < URLOptions.length; i++) {
					var URLOption = URLOptions[i];
					if(URLOption.substr(0, 9) == "track_id=") {
						trackID = parseInt(URLOption.substr(9));
						break;
					}
				};
			}
		}
		
		// Delegate to original handler if the target stream container could not be determined
		if(!trackID || !DOMPlayButton) {
			console.log("UserScript: Failed to determine stream container or track ID for URL: " + url);
			
			return konsole_orig(url);
		}
		
		var DOMStreamContainer = DOMPlayButton.parentNode.parentNode;
		
		// Generate title string from stream information
		var DOMStreamTitle = DOMStreamContainer.querySelector(".textbox > h3");
		var DOMStreamDate  = DOMStreamContainer.querySelector(".textbox > .datum");
		
		var title = "";
		if(DOMStreamTitle) {
			title += DOMStreamTitle.textContent.trim();
			
			if(title.substr(title.length - 1) == "*") {
				title = title.substr(0, title.length - 1).trim();
			}
		}
		if(DOMStreamDate) {
			title += (title != "") ? " vom " : "";
			title += DOMStreamDate.textContent.split("|")[1].trim();
		}
		
		// Hide "Play episode" button(s)
		$A(DOMPlayButton.parentNode.querySelectorAll(".overlay-playbutton")).forEach(function(DOMPlayButton) {
		    DOMPlayButton.style.display = "none";
		});
		
		// Find or add main container below container with information text
		var DOMStreamController = DOMStreamContainer.getElementsByClassName("userscript-audio-controller")[0];
		if(!DOMStreamController) {
			DOMStreamController = document.createElement("div");
			DOMStreamController.className = "userscript-audio-controller";
			DOMStreamContainer.appendChild(DOMStreamController);
		}
		
		// Show progress spinner in controller area
		var DOMLoadingSpinner = document.getElementById("fountainG");
		DOMLoadingSpinner.style.display = "block";
		DOMStreamController.appendChild(DOMLoadingSpinner);
		
		// Download playlist file for stream
		var XHRPlaylist = new XMLHttpRequest();
		XHRPlaylist.addEventListener("load", function() {
			if(XHRPlaylist.status != 200 || !XHRPlaylist.responseXML) {
				var status = XHRPlaylist.status + " " + XHRPlaylist.statusText;
				console.log("UserScript: Could not retrieve playlist file: " + status);
				
				return konsole_orig(url);
			}
			
			// Create list of audio tracks in stream playlist
			var playlist = [];
			$A(XHRPlaylist.responseXML.querySelectorAll("playlist > track")).forEach(function(XMLTrack) {
				var url = XMLTrack.getAttribute("url");
				if(url) {
					playlist.push(url);
				}
			});
			
			// Create <audio> tag (with UI) for episode
			var DOMAudioContainer = document.createElement("div");
			DOMAudioContainer.className = "userscript-audio-container";
			DOMStreamController.appendChild(DOMAudioContainer);
			var DOMAudio = document.createElement("audio");
			DOMAudio.controls = true;
			DOMAudio.preload  = "auto";
			DOMAudio.src      = playlist[0];
			DOMAudioContainer.appendChild(DOMAudio);
			
			// Create download link
			var downloadAttributes = {
				href:     playlist[0],
				download: title,
				target:   "_new",
				type:     "audio/mpeg"
			};
			
			if(navigator.product == "Gecko") {
				// Create <iframe> that contains download link on <audio> domain (thanks Mozilla...)
				var DOMDownloadFrame = document.createElement("iframe");
				DOMDownloadFrame.className = "userscript-audio-download";
				DOMDownloadFrame.src       = "//loopstream01.apa.at/welcome.aspx#DOWNLOAD=" + JSON.stringify(downloadAttributes);
				DOMStreamController.appendChild(DOMDownloadFrame);
			} else {
				// Other browser either support the "download" attribute – or they don't…
				// The one's that don't, won't support streaming the content and will therefor offer it for download
				// regardless... :-)
				DOMStreamController.appendChild(create_download_link(downloadAttributes));
			}
			
			// Hide progress spinner
			DOMLoadingSpinner.style.display = "none";
			
			// Track current playback position
			// (so that playback can be continued after page navigation)
			function setPlaybackHandler() {
				// Do not update state while stream hasn't initialized yet
				if(DOMAudio.readyState < 2) {
					return;
				}
				
				// Store state information for this stream
				try {
					var storage = window.localStorage;
					var keyName = "userscript" + "." + trackID;
					
					if(!DOMAudio.ended) {
						storage.setItem(keyName, JSON.stringify({
							playing:    !DOMAudio.paused,
							currentTime: DOMAudio.currentTime
						}));
					} else {
						storage.removeItem(keyName);
					}
				} catch(e) { console.log(e); /* Handle full storage gracefully */ }
			}
			DOMAudio.addEventListener("play",       setPlaybackHandler);
			DOMAudio.addEventListener("playing",    setPlaybackHandler);
			DOMAudio.addEventListener("seeked",     setPlaybackHandler);
			DOMAudio.addEventListener("timeupdate", setPlaybackHandler);
			DOMAudio.addEventListener("pause",      setPlaybackHandler);
			DOMAudio.addEventListener("ended",      setPlaybackHandler);
			
			// Seek to requested position (once the stream has initialized)
			DOMAudio.addEventListener("loadedmetadata", function() {
				// Read shared playback information
				var state = {};
				try {
					var storage = window.localStorage;
					var keyName = "userscript" + "." + trackID;
					
					state = JSON.parse(storage.getItem(keyName));
				} catch(e) { console.log(e); /* Handle failed JSON.parse() gracefully */ }
				
				if(typeof(state.currentTime) === "number") {
					DOMAudio.currentTime = state.currentTime;
				}
				
				if(state.playing !== true && trackRestore) {
					DOMAudio.pause();
				}
			});
			
			// Start playback
			DOMAudio.play();
		});
		XHRPlaylist.open("GET", "http://oe1.orf.at/programm/" + trackID + "/playlist", true);
		XHRPlaylist.send();
	}, unsafeWindow);
	
	// Replace page read-only stream window launcher function
	Object.defineProperty(unsafeWindow, "konsole", {
		value: konsole
	});
	
	
	
	// Auto-start playback, if the user was previously playing the stream of the current page
	if(pageTrackID) {
		konsole(null, true);
	}
});





function inject_content()
{
	// Inject extra HTML tags for loading indicator
	var DOMLoadingSpinner = document.createElement("div");
	DOMLoadingSpinner.id            = "fountainG";
	DOMLoadingSpinner.style.display = "none";

	for(var i = 1; i <= 8; i++) {
		var DOMLoadingItem = document.createElement("div");
		DOMLoadingItem.id        = "fountainG_" + i;
		DOMLoadingItem.className = "fountainG";
		DOMLoadingSpinner.appendChild(DOMLoadingItem);
	}

	document.body.appendChild(DOMLoadingSpinner);

	// Inject extra page styles for loading indicator
	var DOMStylesheet = document.createElement("style");
	DOMStylesheet.type      = "text/css";
	DOMStylesheet.innerHTML = (function () {/*
		html.download-proxy,
		html.download-proxy > body {
			margin:  0 !important;
			padding: 0 !important;
			height:  100%;
		}
		
		html.download-proxy > body > * {
			display: none !important;
		}
		
		html.download-proxy > body > .userscript-audio-download {
			display: block !important;
		}
		
		
		
		.userscript-audio-controller {
			display: inline-block;
			margin-top:  0.5em;
			margin-left: 196px;
			height: 32px;
		}
		
		.userscript-audio-download {
			text-indent: -9999px;
			width: 64px;
			background: #999494 url("//oe1.orf.at/static/img/ico-tile.png") repeat scroll -100px -1372px;
		}
		
		.userscript-audio-container {
			background-color: #5C5959;
			padding-right: 0.5em;
		}
		
		.userscript-audio-container,
		.userscript-audio-download {
			display: inline-block;
			vertical-align: top;
			height: 100%;
		}
		
		.userscript-audio-controller audio {
			background-color: white;
		}
		
		
		
		.overlay-download-liste,
		.galleryitem > .hover-infobar,
		.galleryitem > .overlay-download {
			display: none !important;
		}
		
		.gallery > .userscript-audio-container {
			margin-top: 0;
		}
		
		
		
		#fountainG{
			position:relative;
			width:84px;
			height:10px;
			margin:auto;
		}

		.fountainG{
			position:absolute;
			top:0;
			background-color:rgb(0,0,0);
			width:10px;
			height:10px;
			animation-name:bounce_fountainG;
				-o-animation-name:bounce_fountainG;
				-ms-animation-name:bounce_fountainG;
				-webkit-animation-name:bounce_fountainG;
				-moz-animation-name:bounce_fountainG;
			animation-duration:1.5s;
				-o-animation-duration:1.5s;
				-ms-animation-duration:1.5s;
				-webkit-animation-duration:1.5s;
				-moz-animation-duration:1.5s;
			animation-iteration-count:infinite;
				-o-animation-iteration-count:infinite;
				-ms-animation-iteration-count:infinite;
				-webkit-animation-iteration-count:infinite;
				-moz-animation-iteration-count:infinite;
			animation-direction:normal;
				-o-animation-direction:normal;
				-ms-animation-direction:normal;
				-webkit-animation-direction:normal;
				-moz-animation-direction:normal;
			transform:scale(.3);
				-o-transform:scale(.3);
				-ms-transform:scale(.3);
				-webkit-transform:scale(.3);
				-moz-transform:scale(.3);
			border-radius:7px;
				-o-border-radius:7px;
				-ms-border-radius:7px;
				-webkit-border-radius:7px;
				-moz-border-radius:7px;
		}

		#fountainG_1{
			left:0;
			animation-delay:0.6s;
				-o-animation-delay:0.6s;
				-ms-animation-delay:0.6s;
				-webkit-animation-delay:0.6s;
				-moz-animation-delay:0.6s;
		}

		#fountainG_2{
			left:10px;
			animation-delay:0.75s;
				-o-animation-delay:0.75s;
				-ms-animation-delay:0.75s;
				-webkit-animation-delay:0.75s;
				-moz-animation-delay:0.75s;
		}

		#fountainG_3{
			left:21px;
			animation-delay:0.9s;
				-o-animation-delay:0.9s;
				-ms-animation-delay:0.9s;
				-webkit-animation-delay:0.9s;
				-moz-animation-delay:0.9s;
		}

		#fountainG_4{
			left:31px;
			animation-delay:1.05s;
				-o-animation-delay:1.05s;
				-ms-animation-delay:1.05s;
				-webkit-animation-delay:1.05s;
				-moz-animation-delay:1.05s;
		}

		#fountainG_5{
			left:42px;
			animation-delay:1.2s;
				-o-animation-delay:1.2s;
				-ms-animation-delay:1.2s;
				-webkit-animation-delay:1.2s;
				-moz-animation-delay:1.2s;
		}

		#fountainG_6{
			left:52px;
			animation-delay:1.35s;
				-o-animation-delay:1.35s;
				-ms-animation-delay:1.35s;
				-webkit-animation-delay:1.35s;
				-moz-animation-delay:1.35s;
		}

		#fountainG_7{
			left:63px;
			animation-delay:1.5s;
				-o-animation-delay:1.5s;
				-ms-animation-delay:1.5s;
				-webkit-animation-delay:1.5s;
				-moz-animation-delay:1.5s;
		}

		#fountainG_8{
			left:73px;
			animation-delay:1.64s;
				-o-animation-delay:1.64s;
				-ms-animation-delay:1.64s;
				-webkit-animation-delay:1.64s;
				-moz-animation-delay:1.64s;
		}



		@keyframes bounce_fountainG{
			0%{
			transform:scale(1);
				background-color:rgb(0,0,0);
			}

			100%{
			transform:scale(.3);
				background-color:rgb(255,255,255);
			}
		}

		@-o-keyframes bounce_fountainG{
			0%{
			-o-transform:scale(1);
				background-color:rgb(0,0,0);
			}

			100%{
			-o-transform:scale(.3);
				background-color:rgb(255,255,255);
			}
		}

		@-ms-keyframes bounce_fountainG{
			0%{
			-ms-transform:scale(1);
				background-color:rgb(0,0,0);
			}

			100%{
			-ms-transform:scale(.3);
				background-color:rgb(255,255,255);
			}
		}

		@-webkit-keyframes bounce_fountainG{
			0%{
			-webkit-transform:scale(1);
				background-color:rgb(0,0,0);
			}

			100%{
			-webkit-transform:scale(.3);
				background-color:rgb(255,255,255);
			}
		}

		@-moz-keyframes bounce_fountainG{
			0%{
			-moz-transform:scale(1);
				background-color:rgb(0,0,0);
			}

			100%{
			-moz-transform:scale(.3);
				background-color:rgb(255,255,255);
			}
		}
	*/}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1];
	document.head.appendChild(DOMStylesheet);
}