Merge branch 'master' into master

This commit is contained in:
Larivact 2018-03-10 08:10:26 +01:00 committed by GitHub
commit 4ae75611e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 140 additions and 76 deletions

View File

@ -1,7 +1,7 @@
# FreeTube
FreeTube is an Open Source Desktop YouTube player built with privacy in mind. Watch your favorite YouTube videos ad free as well as prevent Google from tracking what you watch. Available for Windows / Mac / Linux
Please note that FreeTube is currently in Beta and uses the proprietary [YouTube HTTP API](https://developers.google.com/youtube/v3/). Video URLs are resolved using the [HookTube](https://hooktube.com/) HTTP API.
Please note that FreeTube is currently in Beta and uses the proprietary [YouTube HTTP API](https://developers.google.com/youtube/v3/). Video URLs are resolved using a [youtube-dl-api-server](https://github.com/jaimeMF/youtube-dl-api-server).
<a href='https://github.com/FreeTubeApp/FreeTube/releases' >Download</a>
@ -13,7 +13,7 @@ Please note that FreeTube is currently in Beta and uses the proprietary [YouTube
<img src="https://freetubeapp.github.io/images/FreeTube5.png" width=200 >
# How Does It Work?
FreeTube uses the YouTube API to search for videos. It then uses the HookTube API to grab the raw video files and play them in a basic HTML5 video player, preventing YouTube from tracking you using cookies or JavaScript. Subscriptions, history, and saved videos are stored locally on the user's computer and is never sent out to Google or anyone else. You own your data.
FreeTube uses the YouTube API to search for videos. It then uses the youtube-dl API to grab the raw video files and play them in a basic HTML5 video player, preventing YouTube from tracking you using cookies or JavaScript. Subscriptions, history, and saved videos are stored locally on the user's computer and is never sent out to Google or anyone else. You own your data.
## Features
* Watch videos free of ads

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "FreeTube",
"version": "0.1.2",
"version": "0.1.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,7 +1,7 @@
{
"name": "FreeTube",
"productName": "FreeTube",
"version": "0.1.2",
"version": "0.1.3",
"description": "An Open Source YouTube app for privacy.",
"main": "src/js/init.js",
"scripts": {

View File

@ -34,7 +34,7 @@ if(require('electron-squirrel-startup')) app.quit();
*/
let init = function() {
const Menu = require('electron').Menu;
win = new BrowserWindow({width: 1200, height: 800});
win = new BrowserWindow({width: 1200, height: 800, autoHideMenuBar: true});
win.loadURL(url.format({
pathname: path.join(__dirname, '../index.html'),

View File

@ -44,7 +44,7 @@ function showSettings() {
docs.forEach((setting) => {
switch (setting['_id']) {
case 'apiKey':
if (apiKeyBank.indexOf(setting['value']) < -1) {
if (apiKeyBank.indexOf(setting['value']) == -1) {
key = setting['value'];
}
break;
@ -82,6 +82,7 @@ function showSettings() {
* @return {Void}
*/
function checkDefaultSettings() {
// Grab a random API Key.
apiKey = apiKeyBank[Math.floor(Math.random() * apiKeyBank.length)];
@ -114,7 +115,7 @@ function checkDefaultSettings() {
setTheme(setting['value']);
break;
case 'apiKey':
if (apiKeyBank.indexOf(setting['value']) < -1) {
if (apiKeyBank.indexOf(setting['value']) == -1) {
apiKey = setting['value'];
}
break;
@ -123,8 +124,6 @@ function checkDefaultSettings() {
}
});
}
console.log("Using API key: " + apiKey);
});
}
@ -137,6 +136,11 @@ function updateSettings() {
var themeSwitch = document.getElementById('themeSwitch').checked;
var key = document.getElementById('api-key').value;
// To any third party devs that fork the project, please be ethical and change the API keys.
const apiKeyBank = ['AIzaSyC9E579nh_qqxg6BH4xIce3k_7a9mT4uQc', 'AIzaSyCKplYT6hZIlm2O9FbWTi1G7rkpsLNTq78', 'AIzaSyAE5xzh5GcA_tEDhXmMFd1pEzrL-W7z51E', 'AIzaSyDoFzqwuO9l386eF6BmNkVapjiTJ93CBy4', 'AIzaSyBljfZFPioB0TRJAj-0LS4tlIKl2iucyY4'];
apiKey = apiKeyBank[Math.floor(Math.random() * apiKeyBank.length)];
if (themeSwitch == true) {
var theme = 'dark';
} else {
@ -164,7 +168,7 @@ function updateSettings() {
settingsDb.update({
_id: 'apiKey'
}, {
value: 'AIzaSyDjszXMCw44W_k-pdNoOxUHFyKGtU_ejwE'
value: apiKey
}, {});
}

View File

@ -110,7 +110,7 @@ function loadSubscriptions() {
*/
try {
youtubeAPI('search', {
part: 'snippet',
part: 'snippet', // Try getting content details for video duration in the near future.
channelId: channelId,
type: 'video',
maxResults: 15,

View File

@ -168,6 +168,7 @@ function playVideo(videoId) {
let video720p;
let defaultUrl;
let defaultQuality;
let channelId;
let videoHtml;
let videoThumbnail;
let videoType = 'video';
@ -175,11 +176,14 @@ function playVideo(videoId) {
let validUrl;
// Grab the embeded player. Used as fallback if the video URL cannot be found.
// Also grab the channel ID.
try {
let getEmbedFunction = getEmbedPlayer(videoId);
let getInfoFunction = getChannelAndPlayer(videoId);
getEmbedFunction.then((url) => {
embedPlayer = url;
getInfoFunction.then((data) => {
console.log(data);
embedPlayer = data[0];
channelId = data[1];
});
} catch (ex) {
showToast('Video not found. ID may be invalid.');
@ -188,64 +192,36 @@ function playVideo(videoId) {
}
/*
* FreeTube calls the HookTube API so that it can get the direct video URL instead of the embeded player.
* If anyone knows how to grab these files without relying on their API it would be very welcome.
* It helps that HookTube returns mostly the same information as a YouTube API call so performance
* shouldn't be hindered by this.
* FreeTube calls an instance of a youtube-dl server to grab the direct video URL. Please do not use this API in third party projects.
*/
const url = 'https://hooktube.com/api?mode=video&id=' + videoId;
const url = 'https://stormy-inlet-41826.herokuapp.com/api/info?url=https://www.youtube.com/watch?v=' + videoId + 'flatten=True';
$.getJSON(url, (response) => {
console.log(response);
const videoSummary = response['json_1'];
const videoSnippet = response['json_2']['items'][0]['snippet'];
const videoStatistics = response['json_2']['items'][0]['statistics'];
const info = response['info'];
// Sometimes the max resolution URL isn't found. Grab the default one as a fallback.
try {
videoThumbnail = videoSnippet['thumbnails']['maxres']['url'];
} catch (e) {
videoThumbnail = videoSnippet['thumbnails']['default']['url'];
}
// Search through the returned object to get the 480p and 720p video URLs (If available)
Object.keys(videoSummary['link']).forEach((key) => {
console.log(key);
switch (videoSummary['link'][key][2]) {
case 'medium':
video480p = videoSummary['link'][key][0];
break;
case 'hd720':
video720p = videoSummary['link'][key][0];
break;
}
});
// Default to the embeded player if the URLs cannot be found.
if (typeof(video720p) === 'undefined' && typeof(video480p) === 'undefined') {
defaultQuality = 'EMBED';
videoHtml = embedPlayer.replace(/\&quot\;/g, '"');
showToast('Unable to get video file. Reverting to embeded player.');
} else if (typeof(video720p) === 'undefined' && typeof(video480p) !== 'undefined') {
// Default to the 480p video if the 720p URL cannot be found.
videoHtml = '<video class="videoPlayer" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" src="' + video480p + '" poster="' + videoThumbnail + '" autoplay></video>';
defaultQuality = '480p';
} else {
// Default to the 720p video.
videoHtml = '<video class="videoPlayer" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" src="' + video720p + '" poster="' + videoThumbnail + '" autoplay></video>';
defaultQuality = '720p';
// Force the embeded player if needed.
//videoHtml = embedPlayer;
}
videoThumbnail = info['thumbnail'];
let videoUrls = info['formats'];
// Add commas to the video view count.
const videoViews = videoSummary['view_count'].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
const videoViews = info['view_count'].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
// Format the date to a more readable format.
const dateString = videoSnippet['publishedAt'];
let dateString = info['upload_date'];
dateString = [dateString.slice(0, 4), '-', dateString.slice(4)].join('');
dateString = [dateString.slice(0, 7), '-', dateString.slice(7)].join('');
console.log(dateString);
const publishedDate = dateFormat(dateString, "mmm dS, yyyy");
const channelId = videoSnippet['channelId'];
let description = videoSnippet['description'];
// Figure out the width for the like/dislike bar.
const videoLikes = info['like_count'];
const videoDislikes = info['dislike_count'];
const totalLikes = videoLikes + videoDislikes;
const likePercentage = parseInt((videoLikes / totalLikes) * 100);
let description = info['description'];
// Adds clickable links to the description.
description = autolinker.link(description);
const checkSubscription = isSubscribed(channelId);
@ -271,19 +247,40 @@ function playVideo(videoId) {
}
});
// Figure out the width for the like/dislike bar.
const videoLikes = parseInt(videoStatistics['likeCount']);
const videoDislikes = parseInt(videoStatistics['dislikeCount']);
const totalLikes = videoLikes + videoDislikes;
const likePercentage = parseInt((videoLikes / totalLikes) * 100);
// Search through the returned object to get the 480p and 720p video URLs (If available)
Object.keys(videoUrls).forEach((key) => {
console.log(key);
switch (videoUrls[key]['format_note']) {
case 'medium':
video480p = videoUrls[key]['url'];
break;
case 'hd720':
video720p = videoUrls[key]['url'];
break;
}
});
// Adds clickable links to the description.
description = autolinker.link(description);
// Default to the embeded player if the URLs cannot be found.
if (typeof(video720p) === 'undefined' && typeof(video480p) === 'undefined') {
defaultQuality = 'EMBED';
videoHtml = embedPlayer.replace(/\&quot\;/g, '"');
showToast('Unable to get video file. Reverting to embeded player.');
} else if (typeof(video720p) === 'undefined' && typeof(video480p) !== 'undefined') {
// Default to the 480p video if the 720p URL cannot be found.
videoHtml = '<video class="videoPlayer" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" src="' + video480p + '" poster="' + videoThumbnail + '" autoplay></video>';
defaultQuality = '480p';
} else {
// Default to the 720p video.
videoHtml = '<video class="videoPlayer" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" src="' + video720p + '" poster="' + videoThumbnail + '" autoplay></video>';
defaultQuality = '720p';
// Force the embeded player if needed.
//videoHtml = embedPlayer;
}
// API Request
youtubeAPI('channels', {
'id': channelId,
'part': 'snippet,contentDetails,statistics'
'part': 'snippet'
}, function (data){
const channelThumbnail = data['items'][0]['snippet']['thumbnails']['high']['url'];
@ -292,10 +289,10 @@ function playVideo(videoId) {
const rendered = mustache.render(template, {
videoHtml: videoHtml,
videoQuality: defaultQuality,
videoTitle: videoSummary['title'],
videoTitle: info['title'],
videoViews: videoViews,
videoThumbnail: videoThumbnail,
channelName: videoSummary['author'],
channelName: info['uploader'],
videoLikes: videoLikes,
videoDislikes: videoDislikes,
likePercentage: likePercentage,
@ -484,17 +481,17 @@ function copyLink(website, videoId) {
}
/**
* Get the YouTube embeded player of a video.
* Get the YouTube embeded player of a video as well as channel information..
*
* @param {string} videoId - The video ID of the video to get.
*
* @return {promise} - The HTML of the embeded player
*/
function getEmbedPlayer(videoId) {
function getChannelAndPlayer(videoId) {
console.log(videoId);
return new Promise((resolve, reject) => {
youtubeAPI('videos', {
part: 'player',
part: 'snippet,player',
id: videoId,
}, function (data){
let embedHtml = data.items[0].player.embedHtml;
@ -502,7 +499,7 @@ function getEmbedPlayer(videoId) {
embedHtml = embedHtml.replace('width="480px"', '');
embedHtml = embedHtml.replace('height="270px"', '');
embedHtml = embedHtml.replace(/\"/g, '&quot;');
resolve(embedHtml);
resolve([embedHtml, data.items[0].snippet.channelId]);
});
});
@ -567,6 +564,18 @@ function changeQuality(videoHtml, qualityType, isEmbed = false) {
}
}
/**
* Change the playpack speed of the video.
*
* @param {double} speed - The playback speed of the video.
*
* @return {Void}
*/
function changeVideoSpeed(speed){
$('#currentSpeed').html(speed);
$('.videoPlayer').get(0).playbackRate = speed;
}
/**
* Check to see if the video URLs are valid. Change the video quality if one is not.
* The API will grab video URLs, but they will sometimes return a 404. This

View File

@ -41,6 +41,7 @@ input[type=text] {color: #EEEEEE;}
.recommendDate{color: #E0E0E0;}
.settingsButton {color: #BDBDBD; background-color: #424242;}
.qualityTypes{color: #E0E0E0; background-color: #757575;}
.speedTypes{color: #E0E0E0; background-color: #757575;}
.unsaved{color: #E0E0E0;}
#main{color: #EEEEEE;}

View File

@ -37,6 +37,7 @@ body{background-color: #e0e0e0;}
.recommendDate{color: #616161;}
.settingsButton {color: #424242; background-color: #BDBDBD;}
.qualityTypes{background-color: #eeeeee;}
.speedTypes{background-color: #eeeeee;}
.unsaved{color: #616161;}
#subscriptions img{border: 0px solid #000000;}

View File

@ -178,6 +178,40 @@ iframe{
right: 24px;
}
.qualityTypes ul li{
width: 72px;
position: relative;
right: 15px;
}
.videoSpeed{
width: 42px;
}
.videoSpeed:hover .speedTypes{
visibility: visible;
}
.speedTypes{
visibility: hidden;
width: 72px;
position: relative;
bottom: 10px;
right: 15px;
}
.speedTypes ul{
list-style-type: none;
position: relative;
right: 24px;
}
.speedTypes ul li{
width: 72px;
position: relative;
right: 15px;
}
#showComments{
text-align: center;
height: 40px;

View File

@ -13,6 +13,21 @@
</ul>
</div>
</div>
<div class='smallButton videoSpeed'>
<span id='currentSpeed'>1</span>x <i class="fas fa-angle-down"></i>
<div class='speedTypes'>
<ul>
<li onclick='changeVideoSpeed(0.25)'>0.25x</li>
<li onclick='changeVideoSpeed(0.5)'>0.5x</li>
<li onclick='changeVideoSpeed(0.75)'>0.75x</li>
<li onclick='changeVideoSpeed(1)'>1x</li>
<li onclick='changeVideoSpeed(1.25)'>1.25x</li>
<li onclick='changeVideoSpeed(1.5)'>1.5x</li>
<li onclick='changeVideoSpeed(1.75)'>1.75x</li>
<li onclick='changeVideoSpeed(2)'>2x</li>
</ul>
</div>
</div>
<div onclick='toggleSavedVideo("{{videoId}}")' class='smallButton'>
<i id='saveIcon' style='color: {{savedIconColor}};' class="{{savedIconClass}} fa-star"></i> <span id='savedText'>{{savedText}}</span>
</div>