Merge branch 'development'
|
@ -7,3 +7,5 @@ subscriptions\.db
|
|||
*.db
|
||||
|
||||
electron-packager/win32-x64/FreeTube-win32-x64/
|
||||
|
||||
dist/
|
||||
|
|
|
@ -15,7 +15,6 @@ Please follow these guidlines before sending your pull request and making contri
|
|||
* Comment your code when necessary. Follow the [JavaScript Documentation and Comments Standard](https://www.drupal.org/docs/develop/standards/javascript/javascript-api-documentation-and-comment-standards) for functions.
|
||||
* Please test your code. Make sure new features work as well as core features such as watching videos or loading subscriptions.
|
||||
* Please limit the amount of Node Modules that you introduce into the project. Only include them when absolutely necessary for your code to work (Ex: Using nedb for databases) or if a module provides similar functionality to what you are trying to achieve (Ex: Using autolinker to create links to outside URLs instead of writing the functionality myself).
|
||||
* If using a new Node Module, please include the `require` statement in `layout.js` to keep them together.
|
||||
* Please try to stay involved with the community and maintain your code. I am only one person and I work on FreeTube only in my spare time. I do not have time to work on everything and it would be nice if you can maintain your code when necessary.
|
||||
|
||||
# Setting up Your Environment
|
||||
|
@ -32,6 +31,11 @@ Install Dependencies:
|
|||
npm install
|
||||
```
|
||||
|
||||
Install Dev Dependencies:
|
||||
```
|
||||
npm install --only=dev
|
||||
```
|
||||
|
||||
Run the application:
|
||||
```
|
||||
npm start
|
||||
|
@ -41,12 +45,12 @@ Make / Package application:
|
|||
|
||||
Windows (Requires Wine on Linux):
|
||||
```
|
||||
npm run make:win32
|
||||
npm run make:win
|
||||
```
|
||||
|
||||
Mac:
|
||||
```
|
||||
npm run make:darwin
|
||||
npm run make:mac
|
||||
```
|
||||
|
||||
Linux (Requires deb and rpm to be installed):
|
||||
|
@ -54,4 +58,6 @@ Linux (Requires deb and rpm to be installed):
|
|||
npm run make:linux
|
||||
```
|
||||
|
||||
Other commands are available in the `package.json` file for making specific packages. Refer to `package.json` for instructions.
|
||||
|
||||
I will update this document when necessary. Anyone who has questions or suggestions on this document are welcome to create an issue or pull request.
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1,13 +1,6 @@
|
|||
- [ ] I have read and agree to the [Contribution Guidelines](https://github.com/FreeTubeApp/FreeTube/blob/master/CONTRIBUTING.md)
|
||||
- [ ] I can maintain / support my code if issues arise.
|
||||
|
||||
**Does your change relate to a current issue? Please list it here if applicable.**
|
||||
**Notes and Comments about your pull request**
|
||||
|
||||
|
||||
**Please list out the changes you've made**
|
||||
|
||||
|
||||
**Does your change include any new Node Modules? If yes, what modules and what are they licensed under?**
|
||||
|
||||
|
||||
**Other Comments**
|
||||
|
|
10
README.md
|
@ -1,9 +1,9 @@
|
|||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/jbeguna04/FreeTube/master/LogoDesigns/logotype.png" width=500 align="center">
|
||||
<img src="https://freetubeapp.github.io/images/logoColor.png" width=500 align="center">
|
||||
</p>
|
||||
|
||||
FreeTube is an open source desktop YouTube player built with privacy in mind.
|
||||
Use YouTube without advertisments and Google tracking you using cookies and JavaScript.
|
||||
Use YouTube without advertisments and prevent Google tracking from you with their cookies and JavaScript.
|
||||
Available for Windows, Mac & Linux thanks to Electron.
|
||||
|
||||
Please note that FreeTube is currently in Beta. While it should work well for
|
||||
|
@ -20,7 +20,8 @@ hard coded API keys. Videos are resolved using
|
|||
and played using the stock HTML5 video
|
||||
player. While YouTube can still see your API and video requests it can no
|
||||
longer track you using cookies or JavaScript. Your subscriptions, history, and
|
||||
saved videos are stored locally on your computer and never sent out.
|
||||
saved videos are stored locally on your computer and never sent out. Using a VPN or Tor is recommended
|
||||
to hide your IP while using FreeTube.
|
||||
|
||||
## Screenshots
|
||||
<img src="https://freetubeapp.github.io/images/FreeTube1.png" width=200> <img src="https://freetubeapp.github.io/images/FreeTube2.png" width=200> <img src="https://freetubeapp.github.io/images/FreeTube3.png" width=200> <img src="https://freetubeapp.github.io/images/FreeTube5.png" width=200>
|
||||
|
@ -28,6 +29,7 @@ saved videos are stored locally on your computer and never sent out.
|
|||
## Features
|
||||
* Watch videos without ads
|
||||
* Use YouTube without Google tracking you using cookies and JavaScript
|
||||
* Make API calls through the Tor network
|
||||
* Subscribe to channels without an account
|
||||
* Local subscriptions, history, and saved videos
|
||||
* Export & import subscriptions
|
||||
|
@ -45,6 +47,8 @@ follow the [Contribution
|
|||
Guidelines](https://github.com/FreeTubeApp/FreeTube/blob/master/CONTRIBUTING.md)
|
||||
before sending your pull request.
|
||||
|
||||
Thank you very much to the [People and Projects](https://github.com/FreeTubeApp/FreeTube/wiki/Credits) that make FreeTube possible!
|
||||
|
||||
## License
|
||||
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
{
|
||||
"File": "File",
|
||||
"Quit": "Quit",
|
||||
"Edit": "Edit",
|
||||
"Undo": "Undo",
|
||||
"Redo": "Redo",
|
||||
"Cut": "Cut",
|
||||
"Copy": "Copy",
|
||||
"Paste": "Paste",
|
||||
"Delete": "Delete",
|
||||
"Select all": "Select all",
|
||||
"View": "View",
|
||||
"Reload": "Reload",
|
||||
"Force Reload": "Force Reload",
|
||||
"Toggle Developer Tools": "Toggle Developer Tools",
|
||||
"Actual size": "Actual size",
|
||||
"Zoom in": "Zoom in",
|
||||
"Zoom out": "Zoom out",
|
||||
"Toggle fullscreen": "Toggle fullscreen",
|
||||
"Window": "Window",
|
||||
"Minimize": "Minimize",
|
||||
"Close": "Close",
|
||||
"FreeTube": "FreeTube",
|
||||
"Subscriptions": "Subscriptions",
|
||||
"Featured": "Featured",
|
||||
"Most Popular": "Most Popular",
|
||||
"Saved": "Saved",
|
||||
"Playlists": "Playlists",
|
||||
"History": "History",
|
||||
"Settings": "Settings",
|
||||
"About": "About",
|
||||
"Search / Go to URL": "Search / Go to URL",
|
||||
"Search Results": "Search Results",
|
||||
"Subscriber": "Subscriber",
|
||||
"Subscriber": "Subscribers",
|
||||
"Video": "Video",
|
||||
"Videos": "Videos",
|
||||
"View Full Playlist": "View Full Playlist",
|
||||
"Live Now": "Live Now",
|
||||
"Fetch more results": "Fetch more results",
|
||||
"Fetching results. Please wait": "Fetching results. Please wait",
|
||||
"Latest Subscriptions": "Latest Subscriptions",
|
||||
"Save Video": "Save Video",
|
||||
"Remove Saved Video": "Remove Saved Video",
|
||||
"Open in YouTube": "Open in YouTube",
|
||||
"Copy YouTube Link": "Copy YouTube Link",
|
||||
"Open in HookTube": "Open in HookTube",
|
||||
"Copy HookTube Link": "Copy HookTube Link",
|
||||
"URL has been copied to the clipboard": "URL has been copied to the clipboard",
|
||||
"Found valid URL for 480p, but returned a 404. Video type might be available in the future.": "Found valid URL for 480p, but returned a 404. Video type might be available in the future.",
|
||||
"Save": "Save",
|
||||
"Mini Player": "Mini Player",
|
||||
"View": "View",
|
||||
"Views": "Views",
|
||||
"Subscribe": "Subscribe",
|
||||
"Unsubscribe": "Unsubscribe",
|
||||
"Published on": "Published on",
|
||||
"Jan": "Jan",
|
||||
"Feb": "Feb",
|
||||
"Mar": "Mar",
|
||||
"Apr": "Apr",
|
||||
"May": "May",
|
||||
"Jun": "Jun",
|
||||
"Jul": "Jul",
|
||||
"Aug": "Aug",
|
||||
"Sep": "Sep",
|
||||
"Oct": "Oct",
|
||||
"Nov": "Nov",
|
||||
"Dec": "Dec",
|
||||
"Show Comments": "Show Comments",
|
||||
"Max of 100": "Max of 100",
|
||||
"Recommendations": "Recommendations",
|
||||
"Latest Subscriptions": "Latest Subscriptions",
|
||||
"Getting Subscriptions. Please wait...": "Getting Subscriptions. Please wait...",
|
||||
"Your Subscription list is currently empty. Start adding subscriptions to see them here.": "Your Subscription list is currently empty. Start adding subscriptions to see them here.",
|
||||
"Saved Videos": "Saved Videos",
|
||||
"Watch History": "Watch History",
|
||||
"API Key": "API Key",
|
||||
"Set API Key: Leave blank to use default": "Set API Key: Leave blank to use default",
|
||||
"Use Dark Theme": "Use Dark Theme",
|
||||
"Import Subscriptions": "Import Subscriptions",
|
||||
"Export Subscriptions": "Export Subscriptions",
|
||||
"Clear History": "Clear History",
|
||||
"Are you sure you want to delete your history?": "Are you sure you want to delete your history?",
|
||||
"Clear Saved Videos": "Clear Saved Videos",
|
||||
"Are you sure you want to remove all saved videos?": "Are you sure you want to remove all saved videos?",
|
||||
"Clear Subscriptions": "Clear Subscriptions",
|
||||
"Are you sure you want to remove all subscriptions?": "Are you sure you want to remove all subscriptions?",
|
||||
"Save Settings": "Save Settings",
|
||||
"Yes": "Yes",
|
||||
"No": "No",
|
||||
"Beta": "Beta",
|
||||
"This software is FOSS and released under the GNU Public License v3+.": "This software is FOSS and released under the GNU Public License v3+.",
|
||||
"Found a bug? Want to suggest a feature? Want to help out? Check out our GitHub page. Pull requests are welcome.": "Found a bug? Want to suggest a feature? Want to help out? Check out our GitHub page. Pull requests are welcome."
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
{
|
||||
"File": "Bestand",
|
||||
"Quit": "Afluiten",
|
||||
"Edit": "Bewerken",
|
||||
"Undo": "Ongedaan maken",
|
||||
"Redo": "Opnieuw",
|
||||
"Cut": "Knippen",
|
||||
"Copy": "Kopiëren",
|
||||
"Paste": "Plakken",
|
||||
"Delete": "Verwijderen",
|
||||
"Select all": "Alles selecteren",
|
||||
"View": "Weergave",
|
||||
"Reload": "Herladen",
|
||||
"Force Reload": "Herladen forceren",
|
||||
"Toggle Developer Tools": "Hulpprogramma's voor ontwikkelaars",
|
||||
"Actual size": "Daadwerkelijke grootte",
|
||||
"Zoom in": "Zoom in",
|
||||
"Zoom out": "Zoom uit",
|
||||
"Toggle fullscreen": "Volledig scherm",
|
||||
"Window": "Venster",
|
||||
"Minimize": "Minimaliseren",
|
||||
"Close": "Sluiten",
|
||||
"FreeTube": "FreeTube",
|
||||
"Subscriptions": "Abonnementen",
|
||||
"Featured": "Uitgelicht",
|
||||
"Most Popular": "Populair",
|
||||
"Saved": "Opgeslagen",
|
||||
"Playlists": "Afspeellijsten",
|
||||
"History": "Geschiedenis",
|
||||
"Settings": "Instellingen",
|
||||
"About": "Over",
|
||||
"Search / Go to URL": "Zoeken / Ga naar URL",
|
||||
"Search Results": "Zoekresultaten",
|
||||
"Subscriber": "Abonnee",
|
||||
"Subscriber": "Abonnees",
|
||||
"Video": "Video",
|
||||
"Videos": "Videos",
|
||||
"View Full Playlist": "Volledige afspeellijst weergeven",
|
||||
"Live Now": "Nu Live",
|
||||
"Fetch more results": "Meer resultaten ophalen",
|
||||
"Fetching results. Please wait": "Resultaten ophalen. Een ogenblik geduld",
|
||||
"Latest Subscriptions": "Laatste abonnementen",
|
||||
"Save Video": "Video opslaan",
|
||||
"Remove Saved Video": "Opgeslagen video verwijderen",
|
||||
"Open in YouTube": "Open in YouTube",
|
||||
"Copy YouTube Link": "Kopieer YouTube Link",
|
||||
"Open in HookTube": "Open in HookTube",
|
||||
"Copy HookTube Link": "Kopieer HookTube Link",
|
||||
"URL has been copied to the clipboard": "URL is naar het klembord gekopieerd",
|
||||
"Found valid URL for 480p, but returned a 404. Video type might be available in the future.": "Geldige URL voor 480p gevonden, echter een 404 foutmelding ontvangen. Video is wellicht in de toekomst beschikbaar.",
|
||||
"Save": "Opslaan",
|
||||
"Mini Player": "Mini Speler",
|
||||
"View": "Weergave",
|
||||
"Views": "Weergaven",
|
||||
"Subscribe": "Abboneren",
|
||||
"Unsubscribe": "Afmelden",
|
||||
"Published on": "Gepubliceerd op",
|
||||
"Jan": "Jan",
|
||||
"Feb": "Feb",
|
||||
"Mar": "Maa",
|
||||
"Apr": "Apr",
|
||||
"May": "Mei",
|
||||
"Jun": "Jun",
|
||||
"Jul": "Jul",
|
||||
"Aug": "Aug",
|
||||
"Sep": "Sep",
|
||||
"Oct": "Okt",
|
||||
"Nov": "Nov",
|
||||
"Dec": "Dec",
|
||||
"Show Comments": "Commentaren weergeven",
|
||||
"Max of 100": "Maximaal 100",
|
||||
"Recommendations": "Aanbevelingen",
|
||||
"Latest Subscriptions": "Laatste abonnementen",
|
||||
"Getting Subscriptions. Please wait...": "Abonnementen worden opgehaald. Een ogenblik geduld...",
|
||||
"Your Subscription list is currently empty. Start adding subscriptions to see them here.": "Deze lijst is momenteel leeg. Voeg abonnementen toe om ze hier terug te zien.",
|
||||
"Saved Videos": "Opgeslagen Videos",
|
||||
"Watch History": "Geschiedenis",
|
||||
"API Key": "API Sleutel",
|
||||
"Set API Key: Leave blank to use default": "API sleutel instellen: Laat leeg om standaard te gebruiken",
|
||||
"Use Dark Theme": "Donker thema gebruiken",
|
||||
"Import Subscriptions": "Importeer Abonnementen",
|
||||
"Export Subscriptions": "Exporteer Abonnementen",
|
||||
"Clear History": "Geschiedenis wissen",
|
||||
"Are you sure you want to delete your history?": "Weet u zeker dat u de geschiedenis wilt wissen?",
|
||||
"Clear Saved Videos": "Opgeslagen videos verwijderen",
|
||||
"Are you sure you want to remove all saved videos?": "Weet u zeker dat u alle opgeslagen videos wilt verwijderen?",
|
||||
"Clear Subscriptions": "Abonnementen verwijderen",
|
||||
"Are you sure you want to remove all subscriptions?": "Weet u zeker dat u alle abonnementen wilt verwijderen?",
|
||||
"Save Settings": "Instellingen opslaan",
|
||||
"Yes": "Ja",
|
||||
"No": "Nee",
|
||||
"Beta": "Beta",
|
||||
"This software is FOSS and released under the GNU Public License v3+.": "Deze software is FOSS en gepubliceerd onder de GNU Public License v3+.",
|
||||
"Found a bug? Want to suggest a feature? Want to help out? Check out our GitHub page. Pull requests are welcome.": "Een bug gevonden? Graag iets toegevoegd zien? Wilt u helpen bij de ontwikkeling? Check de GitHub pagina. Pull requests zijn welkom."
|
||||
}
|
53
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "FreeTube",
|
||||
"productName": "FreeTube",
|
||||
"version": "0.2.1",
|
||||
"version": "0.3.0",
|
||||
"description": "An Open Source YouTube app for privacy.",
|
||||
"main": "src/js/init.js",
|
||||
"scripts": {
|
||||
|
@ -9,9 +9,16 @@
|
|||
"package": "electron-forge package",
|
||||
"make": "electron-forge make",
|
||||
"publish": "electron-forge publish",
|
||||
"make:darwin": "electron-forge make --platform=darwin",
|
||||
"make:mac": "electron-forge make --platform=darwin",
|
||||
"make:linux": "electron-forge make --platform=linux",
|
||||
"make:win32": "electron-forge make --platform=win32"
|
||||
"make:linux:zip": "electron-forge make --platform=linux --targets=zip",
|
||||
"make:deb": "electron-forge make --platform=linux --targets=deb",
|
||||
"make:rpm": "electron-forge make --platform=linux --targets=rpm",
|
||||
"make:snap": "electron-forge package && electron-installer-snap --src=out/FreeTube-linux-x64",
|
||||
"make:flatpak": "electron-installer-flatpak --src out/FreeTube-linux-x64/ --dest out/make --arch x64",
|
||||
"make:appimage": "electron-forge make --platform=linux --targets=electron-forge-maker-appimage",
|
||||
"make:win": "electron-forge make --platform=win32",
|
||||
"make:win:zip": "electron-forge make --platform=win32 --targets=zip"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": {
|
||||
|
@ -19,32 +26,44 @@
|
|||
"email": "FreeTubeApp@protonmail.com",
|
||||
"url": "https://github.com/FreeTubeApp/FreeTube"
|
||||
},
|
||||
"license": "GPL-3.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"config": {
|
||||
"forge": {
|
||||
"make_targets": {
|
||||
"win32": [
|
||||
"squirrel"
|
||||
"squirrel",
|
||||
"zip"
|
||||
],
|
||||
"darwin": [
|
||||
"zip"
|
||||
],
|
||||
"linux": [
|
||||
"deb",
|
||||
"rpm"
|
||||
"rpm",
|
||||
"electron-forge-maker-appimage",
|
||||
"zip"
|
||||
]
|
||||
},
|
||||
"protocols": [
|
||||
{
|
||||
"name": "freetube",
|
||||
"role": "Viewer",
|
||||
"schemes": [
|
||||
"freetube"
|
||||
]
|
||||
}
|
||||
],
|
||||
"electronPackagerConfig": {
|
||||
"packageManager": "yarn",
|
||||
"icon": "./src/icons/icon.icns"
|
||||
"icon": "./src/icons/iconColor.icns"
|
||||
},
|
||||
"electronWinstallerConfig": {
|
||||
"name": "freetube",
|
||||
"iconUrl": "https://raw.githubusercontent.com/FreeTubeApp/FreeTubeApp.github.io/master/images/icon.ico",
|
||||
"setupIcon": "./src/icons/icon.ico"
|
||||
"iconUrl": "https://raw.githubusercontent.com/FreeTubeApp/FreeTubeApp.github.io/master/images/iconColor.ico",
|
||||
"setupIcon": "./src/icons/iconColor.ico"
|
||||
},
|
||||
"electronInstallerDebian": {
|
||||
"icon": "src/icons/icon.svg"
|
||||
"icon": "src/icons/iconColor.png"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -53,18 +72,24 @@
|
|||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron-forge": "^5.1.1",
|
||||
"electron-prebuilt-compile": "1.8.2",
|
||||
"electron-forge": "^5.2.2",
|
||||
"electron-forge-maker-appimage": "^20.14.4",
|
||||
"electron-installer-flatpak": "^0.8.0",
|
||||
"electron-installer-snap": "^2.0.1",
|
||||
"electron-prebuilt-compile": "2.0.2",
|
||||
"electron-winstaller": "^2.6.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"autolinker": "^1.6.2",
|
||||
"dateformat": "^3.0.3",
|
||||
"electron-compile": "^6.4.2",
|
||||
"electron-compile": "6.4.2",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"github-version-checker": "^2.0.1",
|
||||
"jquery": "^3.3.1",
|
||||
"mustache": "^2.3.0",
|
||||
"nedb": "^1.8.0",
|
||||
"opml-to-json": "0.0.3"
|
||||
"opml-to-json": "0.0.3",
|
||||
"tor-request": "^2.1.2",
|
||||
"ytdl-core": "^0.20.4"
|
||||
}
|
||||
}
|
||||
|
|
3729
src/icons/icon.icns
Before Width: | Height: | Size: 361 KiB |
Before Width: | Height: | Size: 39 KiB |
|
@ -1,83 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="222mm"
|
||||
height="222mm"
|
||||
viewBox="0 0 222 222"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
|
||||
sodipodi:docname="icon.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.35"
|
||||
inkscape:cx="-100"
|
||||
inkscape:cy="445.71429"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:pagecheckerboard="false"
|
||||
inkscape:window-width="1720"
|
||||
inkscape:window-height="1336"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="0" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-75)">
|
||||
<circle
|
||||
id="path3711"
|
||||
cx="110.74702"
|
||||
cy="186.25298"
|
||||
r="102.43154"
|
||||
style="fill:#eeeeee;fill-opacity:1;stroke:#f44336;stroke-width:15.02900028;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stroke-linecap:round;stroke-linejoin:bevel" />
|
||||
<path
|
||||
sodipodi:type="star"
|
||||
style="fill:#f44336;fill-opacity:1;stroke-width:0.26458332"
|
||||
id="path4520"
|
||||
sodipodi:sides="3"
|
||||
sodipodi:cx="108.25239"
|
||||
sodipodi:cy="186.70654"
|
||||
sodipodi:r1="65.785233"
|
||||
sodipodi:r2="32.892616"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:arg2="1.0471976"
|
||||
inkscape:flatsided="false"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="m 174.03762,186.70654 -49.33893,28.48585 -49.338918,28.48584 0,-56.97169 0,-56.97168 49.338928,28.48584 z"
|
||||
inkscape:transform-center-x="-16.446307"
|
||||
inkscape:transform-center-y="-1.0179227e-06" />
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 252 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 9.3 KiB |
After Width: | Height: | Size: 1.7 KiB |
|
@ -13,6 +13,7 @@
|
|||
<link rel="stylesheet" href="style/fontawesome-all.min.css">
|
||||
<script src="js/youtubeApi.js"></script>
|
||||
<script src="js/settings.js"></script>
|
||||
<script src="js/updates.js"></script>
|
||||
<script src="js/layout.js"></script>
|
||||
<script src="js/videos.js"></script>
|
||||
<script src="js/player.js"></script>
|
||||
|
@ -41,26 +42,29 @@
|
|||
<i onclick='hideToast()' class="closeToast fas fa-times"></i>
|
||||
</div>
|
||||
<div class="topNav">
|
||||
<span class="logo"><i onclick='toggleSideNavigation()' class="menuButton fas fa-bars"></i> FreeTube <i class="far fa-play-circle"></i></span>
|
||||
<i onclick='toggleSideNavigation()' class="fas fa-bars" id='menuButton'></i>
|
||||
<div class="searchBar">
|
||||
<input id='jumpToInput' class='jumpToInput' type='text' placeholder="Jump to Video Link / Id" /><i onclick='parseVideoLink()' class="fas fa-search searchButton" style='margin-right: 50px; cursor: pointer;'></i>
|
||||
<input id='search' class="search" type="text" placeholder="Search">
|
||||
<i onclick='search()' class="fas fa-search searchButton" style='margin-right: -10px; cursor: pointer'></i>
|
||||
<input id='search' class="search" type="text" placeholder="Search / Go to URL">
|
||||
<i onclick='parseSearchText()' class="fas fa-search searchButton" style='margin-right: -10px; cursor: pointer'></i>
|
||||
</div>
|
||||
<img src='icons/iconBlack.png' id='menuIcon'/>
|
||||
|
||||
<img src='icons/textBlack.png' id='menuText'/>
|
||||
</div>
|
||||
<div id='sideNavDisabled'></div>
|
||||
<div id="sideNav">
|
||||
<div class="sideNavContainer">
|
||||
<ul>
|
||||
<li onclick='loadSubscriptions()'><i class="fas fa-users"></i> Subscriptions</li>
|
||||
<li onclick='showMostPopular()'><i class="fas fa-fire"></i> Most Popular</li>
|
||||
<li onclick='loadSubscriptions()'><i class="fas fa-rss"></i> Subscriptions</li>
|
||||
<li onclick='showMostPopular()'><i class="fas fa-users"></i> Most Popular</li>
|
||||
<li onclick='showSavedVideos()'><i class="fas fa-star"></i> Saved</li>
|
||||
<li onclick='showHistory()'><i class="fas fa-history"></i> History</li>
|
||||
</ul>
|
||||
<hr />
|
||||
<ul>
|
||||
<li onclick='showSettings()'><i class="fas fa-cog"></i> Settings</li>
|
||||
<li onclick='showAbout()'><i class="far fa-question-circle"></i> About</li>
|
||||
<li onclick='showSettings()'><i class="fas fa-sliders-h"></i> Settings</li>
|
||||
<li onclick='showAbout()'><i class="fas fa-info-circle"></i> About</li>
|
||||
<a href="https://github.com/FreeTubeApp/FreeTube/wiki" style='text-decoration: none; color: inherit;'><li><i class="fas fa-question-circle"></i> Help</li></a>
|
||||
</ul>
|
||||
<hr />
|
||||
<ul id='subscriptions'>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
/*
|
||||
This file is part of FreeTube.
|
||||
|
||||
FreeTube is free software: you can redistribute it and/or modify
|
||||
|
@ -115,8 +115,7 @@ let videoShortcutHandler = function(event) {
|
|||
}
|
||||
break;
|
||||
case 67:
|
||||
// F Key
|
||||
event.preventDefault();
|
||||
// C Key
|
||||
let subtitleMode = $('.videoPlayer').get(0).textTracks[0].mode;
|
||||
if (subtitleMode === 'hidden'){
|
||||
$('.videoPlayer').get(0).textTracks[0].mode = 'showing'
|
||||
|
@ -205,7 +204,7 @@ let fullscreenVideo = function(event){
|
|||
}
|
||||
else{
|
||||
$('.videoPlayer').get(0).webkitRequestFullscreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,12 +20,26 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
|
|||
/*
|
||||
* File used to initializing the application
|
||||
*/
|
||||
const {app, BrowserWindow, dialog} = require('electron');
|
||||
const {app, BrowserWindow, dialog, protocol} = require('electron');
|
||||
const path = require('path');
|
||||
const url = require('url');
|
||||
let win;
|
||||
|
||||
if(require('electron-squirrel-startup')) app.quit();
|
||||
protocol.registerStandardSchemes(['freetube']);
|
||||
|
||||
app.setAsDefaultProtocolClient('freetube');
|
||||
|
||||
const isSecondInstance = app.makeSingleInstance((commandLine, workingDirectory) => {
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (win) {
|
||||
if (win.isMinimized()) win.restore()
|
||||
win.focus()
|
||||
|
||||
win.webContents.send('ping', commandLine)
|
||||
}
|
||||
});
|
||||
|
||||
if(require('electron-squirrel-startup') || isSecondInstance) app.quit();
|
||||
|
||||
/**
|
||||
* Initialize the Electron application
|
||||
|
@ -34,6 +48,7 @@ if(require('electron-squirrel-startup')) app.quit();
|
|||
*/
|
||||
let init = function() {
|
||||
const Menu = require('electron').Menu;
|
||||
|
||||
win = new BrowserWindow({width: 1200, height: 800, autoHideMenuBar: true});
|
||||
|
||||
win.loadURL(url.format({
|
||||
|
@ -43,7 +58,7 @@ let init = function() {
|
|||
}));
|
||||
|
||||
if (process.env = 'development') {
|
||||
//win.webContents.openDevTools();
|
||||
//win.webContents.openDevTools();ff
|
||||
}
|
||||
|
||||
win.on('closed', () => {
|
||||
|
@ -51,15 +66,18 @@ let init = function() {
|
|||
});
|
||||
|
||||
const template = [
|
||||
{
|
||||
label: 'File',
|
||||
submenu: [
|
||||
{role: 'quit'}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{role: 'undo'},
|
||||
{role: 'redo'},
|
||||
{type: 'separator'},
|
||||
{role: 'cut'},
|
||||
{role: 'copy'},
|
||||
{role: 'paste'},
|
||||
{role: 'copy', accelerator: "CmdOrCtrl+C", selector: "copy:" },
|
||||
{role: 'paste', accelerator: "CmdOrCtrl+V", selector: "paste:" },
|
||||
{role: 'pasteandmatchstyle'},
|
||||
{role: 'delete'},
|
||||
{role: 'selectall'}
|
||||
|
@ -96,6 +114,8 @@ let init = function() {
|
|||
* Quit the application
|
||||
*/
|
||||
let allWindowsClosed = function() {
|
||||
win.webContents.session.clearStorageData([], (data) => {});
|
||||
win.webContents.session.clearCache((data) => {});
|
||||
app.quit();
|
||||
};
|
||||
|
||||
|
|
194
src/js/layout.js
|
@ -18,9 +18,9 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
||||
|
||||
/*
|
||||
* File for main layout manipulation and general variable configuration.
|
||||
* There are some functions from other files that will probably need to be moved to here.
|
||||
*/
|
||||
* File for main layout manipulation and general variable configuration.
|
||||
* There are some functions from other files that will probably need to be moved to here.
|
||||
*/
|
||||
|
||||
// Add general variables. Please put all require statements here.
|
||||
const Datastore = require('nedb'); // database logic
|
||||
|
@ -28,28 +28,34 @@ window.$ = window.jQuery = require('jquery');
|
|||
const mustache = require('mustache'); // templating
|
||||
const dateFormat = require('dateformat'); // formatting dates
|
||||
|
||||
//const RxPlayer = require('rx-player'); // formatting dates
|
||||
|
||||
// Used for finding links within text and making them clickable. Used mostly for video descriptions.
|
||||
const autolinker = require('autolinker');
|
||||
const electron = require('electron');
|
||||
const protocol = electron.remote.protocol;
|
||||
|
||||
// Used for getting the user's subscriptions. Can probably remove this when that function
|
||||
// is rewritten.
|
||||
//const asyncLoop = require('node-async-loop');
|
||||
//const youtubedl = require('youtube-dl');
|
||||
const ytdl = require('ytdl-core');
|
||||
const shell = electron.shell; // Used to open external links into the user's native browser.
|
||||
const localDataStorage = electron.remote.app.getPath('userData'); // Grabs the userdata directory based on the user's OS
|
||||
const clipboard = electron.clipboard;
|
||||
const getOpml = require('opml-to-json'); // Gets the file type for imported files.
|
||||
const fs = require('fs'); // Used to read files. Specifically in the settings page.
|
||||
const tor = require('tor-request');
|
||||
|
||||
let currentTheme = '';
|
||||
let apiKey;
|
||||
let dialog = require('electron').remote.dialog; // Used for opening file browser to export / import subscriptions.
|
||||
let useTor = false;
|
||||
let dialog = electron.remote.dialog; // Used for opening file browser to export / import subscriptions.
|
||||
let toastTimeout; // Timeout for toast notifications.
|
||||
let mouseTimeout; // Timeout for hiding the mouse cursor on video playback
|
||||
|
||||
require.extensions['.html'] = function (module, filename) {
|
||||
module.exports = fs.readFileSync(filename, 'utf8');
|
||||
require.extensions['.html'] = function(module, filename) {
|
||||
module.exports = fs.readFileSync(filename, 'utf8');
|
||||
};
|
||||
|
||||
const subDb = new Datastore({
|
||||
|
@ -68,7 +74,7 @@ const savedVidsDb = new Datastore({
|
|||
});
|
||||
|
||||
const settingsDb = new Datastore({
|
||||
filename: localDataStorage + 'settings.db',
|
||||
filename: localDataStorage + '/settings.db',
|
||||
autoload: true
|
||||
});
|
||||
|
||||
|
@ -76,15 +82,21 @@ const settingsDb = new Datastore({
|
|||
// none are found.
|
||||
checkDefaultSettings();
|
||||
|
||||
// Ppen links externally by default
|
||||
$(document).on('click', 'a[href^="http"]', (event) =>{
|
||||
require('electron').ipcRenderer.on('ping', function(event, message) {
|
||||
let url = message[1].replace('freetube://', '');
|
||||
parseSearchText(url);
|
||||
console.log(message); // Prints "whoooooooh!"
|
||||
});
|
||||
|
||||
// Open links externally by default
|
||||
$(document).on('click', 'a[href^="http"]', (event) => {
|
||||
let el = event.currentTarget;
|
||||
event.preventDefault();
|
||||
shell.openExternal(el.href);
|
||||
});
|
||||
|
||||
// Open links externally on middle click.
|
||||
$(document).on('auxclick', 'a[href^="http"]', (event) =>{
|
||||
$(document).on('auxclick', 'a[href^="http"]', (event) => {
|
||||
let el = event.currentTarget;
|
||||
event.preventDefault();
|
||||
shell.openExternal(el.href);
|
||||
|
@ -101,14 +113,7 @@ $(document).ready(() => {
|
|||
// Allow user to use the 'enter' key to search for a video.
|
||||
searchBar.onkeypress = (e) => {
|
||||
if (e.keyCode === 13) {
|
||||
search();
|
||||
}
|
||||
};
|
||||
|
||||
// Allow user to use the 'enter' key to open a video link.
|
||||
jumpToInput.onkeypress = (e) => {
|
||||
if (e.keyCode === 13) {
|
||||
parseVideoLink();
|
||||
parseSearchText();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -118,10 +123,10 @@ $(document).ready(() => {
|
|||
});
|
||||
|
||||
/**
|
||||
* Toggle the ability to view the side navigation bar.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
* Toggle the ability to view the side navigation bar.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function toggleSideNavigation() {
|
||||
const sideNav = document.getElementById('sideNav');
|
||||
const mainContainer = document.getElementById('main');
|
||||
|
@ -136,10 +141,10 @@ function toggleSideNavigation() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Clears out the #main container to allow other information to be shown.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
* Clears out the #main container to allow other information to be shown.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function clearMainContainer() {
|
||||
const container = document.getElementById('main');
|
||||
container.innerHTML = '';
|
||||
|
@ -150,32 +155,35 @@ function startLoadingAnimation() {
|
|||
const loading = document.getElementById('loading');
|
||||
const sideNavDisabled = document.getElementById('sideNavDisabled');
|
||||
const searchBar = document.getElementById('search');
|
||||
const goToVideoInput = document.getElementById('jumpToInput');
|
||||
|
||||
loading.style.display = 'inherit';
|
||||
sideNavDisabled.style.display = 'inherit';
|
||||
if(sideNavDisabled !== null){
|
||||
sideNavDisabled.style.display = 'inherit';
|
||||
}
|
||||
|
||||
searchBar.disabled = true;
|
||||
goToVideoInput.disabled = true;
|
||||
}
|
||||
|
||||
function stopLoadingAnimation() {
|
||||
const loading = document.getElementById('loading');
|
||||
const sideNavDisabled = document.getElementById('sideNavDisabled');
|
||||
const searchBar = document.getElementById('search');
|
||||
const goToVideoInput = document.getElementById('jumpToInput');
|
||||
|
||||
loading.style.display = 'none';
|
||||
sideNavDisabled.style.display = 'none';
|
||||
if(sideNavDisabled !== null){
|
||||
sideNavDisabled.style.display = 'none';
|
||||
}
|
||||
|
||||
searchBar.disabled = false;
|
||||
goToVideoInput.disabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a div container in #main meant to be a container for video lists.
|
||||
*
|
||||
* @param {string} headerLabel - The header of the container. Not used for showing video recommendations.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
* Creates a div container in #main meant to be a container for video lists.
|
||||
*
|
||||
* @param {string} headerLabel - The header of the container. Not used for showing video recommendations.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function createVideoListContainer(headerLabel = '') {
|
||||
const videoListContainer = document.createElement("div");
|
||||
videoListContainer.id = 'videoListContainer';
|
||||
|
@ -191,11 +199,11 @@ function createVideoListContainer(headerLabel = '') {
|
|||
}
|
||||
|
||||
/**
|
||||
* Displays the about page to #main
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function showAbout(){
|
||||
* Displays the about page to #main
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function showAbout() {
|
||||
// Remove current information and display loading animation
|
||||
clearMainContainer();
|
||||
startLoadingAnimation();
|
||||
|
@ -211,14 +219,14 @@ function showAbout(){
|
|||
}
|
||||
|
||||
/**
|
||||
* Display a toast message in the bottom right corner of the page.
|
||||
* The toast automatically disappears after a timeout.
|
||||
*
|
||||
* @param {string} message - The toast message.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function showToast(message){
|
||||
* Display a toast message in the bottom right corner of the page.
|
||||
* The toast automatically disappears after a timeout.
|
||||
*
|
||||
* @param {string} message - The toast message.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function showToast(message) {
|
||||
let toast = document.getElementById('toast');
|
||||
let toastMessage = document.getElementById('toastMessage');
|
||||
|
||||
|
@ -234,27 +242,27 @@ function showToast(message){
|
|||
}
|
||||
|
||||
/**
|
||||
* Hide the toast notification from the page.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function hideToast(){
|
||||
* Hide the toast notification from the page.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function hideToast() {
|
||||
let toast = document.getElementById('toast');
|
||||
toast.style.opacity = 0;
|
||||
toast.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a confirmation box before performing an action. The action will be performed
|
||||
* if the user clicks 'yes'.
|
||||
*
|
||||
* @param {string} message - The message to be displayed in the confirmation box
|
||||
* @param {function} performFunction - The function to be performed upon confirmation
|
||||
* @param {*} parameters - The parameters that will be sent to performFunction
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function confirmFunction(message, performFunction, parameters){
|
||||
* Displays a confirmation box before performing an action. The action will be performed
|
||||
* if the user clicks 'yes'.
|
||||
*
|
||||
* @param {string} message - The message to be displayed in the confirmation box
|
||||
* @param {function} performFunction - The function to be performed upon confirmation
|
||||
* @param {*} parameters - The parameters that will be sent to performFunction
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function confirmFunction(message, performFunction, parameters = '') {
|
||||
let confirmContainer = document.getElementById('confirmFunction');
|
||||
let confirmMessage = document.getElementById('confirmMessage');
|
||||
|
||||
|
@ -262,50 +270,54 @@ function confirmFunction(message, performFunction, parameters){
|
|||
confirmContainer.style.visibility = 'visible';
|
||||
|
||||
$(document).on('click', '#confirmYes', (event) => {
|
||||
performFunction(parameters);
|
||||
if(parameters != ''){
|
||||
performFunction(parameters);
|
||||
}
|
||||
else{
|
||||
performFunction();
|
||||
}
|
||||
hideConfirmFunction();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the confirmation box. Happens when the user clicks on 'no'.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function hideConfirmFunction(){
|
||||
* Hides the confirmation box. Happens when the user clicks on 'no'.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function hideConfirmFunction() {
|
||||
let confirmContainer = document.getElementById('confirmFunction');
|
||||
confirmContainer.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the mouse cursor after ~3 seconds. Used to hide the video when the user
|
||||
* hovers the mouse over the video player.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function hideMouseTimeout(){
|
||||
* Hide the mouse cursor after ~3 seconds. Used to hide the video when the user
|
||||
* hovers the mouse over the video player.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function hideMouseTimeout() {
|
||||
$('.videoPlayer')[0].style.cursor = 'default';
|
||||
clearTimeout(mouseTimeout);
|
||||
mouseTimeout = window.setTimeout(function(){
|
||||
mouseTimeout = window.setTimeout(function() {
|
||||
$('.videoPlayer')[0].style.cursor = 'none';
|
||||
}, 3150);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the timeout for the mouse cursor as a fallback.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function removeMouseTimeout(){
|
||||
* Remove the timeout for the mouse cursor as a fallback.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function removeMouseTimeout() {
|
||||
$('.videoPlayer')[0].style.cursor = 'default';
|
||||
clearTimeout(mouseTimeout);
|
||||
}
|
||||
|
||||
function showVideoOptions(element){
|
||||
if (element.nextElementSibling.style.display == 'none' || element.nextElementSibling.style.display == ''){
|
||||
element.nextElementSibling.style.display = 'inline-block'
|
||||
}
|
||||
else{
|
||||
element.nextElementSibling.style.display = 'none'
|
||||
}
|
||||
function showVideoOptions(element) {
|
||||
if (element.nextElementSibling.style.display == 'none' || element.nextElementSibling.style.display == '') {
|
||||
element.nextElementSibling.style.display = 'inline-block'
|
||||
} else {
|
||||
element.nextElementSibling.style.display = 'none'
|
||||
}
|
||||
}
|
||||
|
|
223
src/js/player.js
|
@ -28,9 +28,14 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
|
|||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function playVideo(videoId) {
|
||||
clearMainContainer();
|
||||
startLoadingAnimation();
|
||||
function playVideo(videoId, videoThumbnail = '', useWindowPlayer = false) {
|
||||
if (useWindowPlayer === false){
|
||||
clearMainContainer();
|
||||
startLoadingAnimation();
|
||||
}
|
||||
else{
|
||||
showToast('Getting video information. Please wait...')
|
||||
}
|
||||
|
||||
let subscribeText = '';
|
||||
let savedText = '';
|
||||
|
@ -42,32 +47,20 @@ function playVideo(videoId) {
|
|||
let subtitleHtml = '';
|
||||
let subtitleLabel;
|
||||
let subtitleLanguage;
|
||||
let subtitleCode;
|
||||
let subtitleUrl;
|
||||
let defaultUrl;
|
||||
let defaultQuality;
|
||||
let channelId;
|
||||
let videoHtml;
|
||||
let videoThumbnail;
|
||||
let videoType = 'video';
|
||||
let embedPlayer;
|
||||
let embedPlayer = "<iframe width='560' height='315' src='https://www.youtube-nocookie.com/embed/" + videoId + "?rel=0' frameborder='0' allow='autoplay; encrypted-media' allowfullscreen></iframe>";
|
||||
let useEmbedPlayer = false;
|
||||
let validUrl;
|
||||
|
||||
// Grab the embeded player. Used as fallback if the video URL cannot be found.
|
||||
// Also grab the channel ID.
|
||||
try {
|
||||
let getInfoFunction = getChannelAndPlayer(videoId);
|
||||
|
||||
getInfoFunction.then((data) => {
|
||||
console.log(data);
|
||||
embedPlayer = data[0];
|
||||
channelId = data[1];
|
||||
});
|
||||
} catch (ex) {
|
||||
showToast('Video not found. ID may be invalid.');
|
||||
stopLoadingAnimation();
|
||||
return;
|
||||
}
|
||||
let videoLikes;
|
||||
let videoDislikes;
|
||||
let totalLikes;
|
||||
let likePercentage;
|
||||
|
||||
const checkSavedVideo = videoIsSaved(videoId);
|
||||
|
||||
|
@ -82,58 +75,56 @@ function playVideo(videoId) {
|
|||
}
|
||||
});
|
||||
|
||||
youtubeAPI('videos', {
|
||||
part: 'statistics',
|
||||
id: videoId,
|
||||
}, function(data) {
|
||||
console.log(data);
|
||||
|
||||
// Figure out the width for the like/dislike bar.
|
||||
videoLikes = data['items'][0]['statistics']['likeCount'];
|
||||
videoDislikes = data['items'][0]['statistics']['dislikeCount'];
|
||||
totalLikes = parseInt(videoLikes) + parseInt(videoDislikes);
|
||||
likePercentage = parseInt((videoLikes / totalLikes) * 100);
|
||||
});
|
||||
|
||||
/*
|
||||
* FreeTube calls youtube-dl to grab the direct video URL.
|
||||
*/
|
||||
youtubedlGetInfo(videoId, (info) => {
|
||||
console.log(info);
|
||||
|
||||
videoThumbnail = info['thumbnail'];
|
||||
console.log(videoLikes);
|
||||
|
||||
channelId = info['author']['id'];
|
||||
let channelThumbnail = info['author']['avatar'];
|
||||
|
||||
let videoUrls = info['formats'];
|
||||
|
||||
// Add commas to the video view count.
|
||||
const videoViews = info['view_count'].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
|
||||
// Format the date to a more readable format.
|
||||
let dateString = info['upload_date'];
|
||||
dateString = [dateString.slice(0, 4), '-', dateString.slice(4)].join('');
|
||||
dateString = [dateString.slice(0, 7), '-', dateString.slice(7)].join('');
|
||||
const publishedDate = dateFormat(dateString, "mmm dS, yyyy");
|
||||
videoThumbnail = info['player_response']['videoDetails']['thumbnail']['thumbnails'][3]['url'];
|
||||
|
||||
// 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);
|
||||
// Format the date to a more readable format.
|
||||
let dateString = new Date(info['published']);
|
||||
dateString.setDate(dateString.getDate() + 1);
|
||||
const publishedDate = dateFormat(dateString, "mmm dS, yyyy");
|
||||
|
||||
let description = info['description'];
|
||||
// Adds clickable links to the description.
|
||||
description = autolinker.link(description);
|
||||
|
||||
if (info['subtitles'] !== null) {
|
||||
videoSubtitles = info['subtitles'];
|
||||
|
||||
// Grab all subtitles
|
||||
Object.keys(videoSubtitles).forEach((subtitle) => {
|
||||
subtitleLabel = subtitle.toUpperCase();
|
||||
subtitleUrl = videoSubtitles[subtitle][1]['url'];
|
||||
|
||||
if (subtitle === 'en') {
|
||||
subtitleHtml = subtitleHtml + '<track label="' + subtitleLabel + '" kind="subtitles" srclang="' + subtitle + '" src="' + subtitleUrl + '" default>';
|
||||
} else {
|
||||
subtitleHtml = subtitleHtml + '<track label="' + subtitleLabel + '" kind="subtitles" srclang="' + subtitle + '" src="' + subtitleUrl + '">';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Search through the returned object to get the 480p and 720p video URLs (If available)
|
||||
Object.keys(videoUrls).forEach((key) => {
|
||||
switch (videoUrls[key]['format_id']) {
|
||||
switch (videoUrls[key]['itag']) {
|
||||
case '18':
|
||||
video480p = videoUrls[key]['url'];
|
||||
console.log(video480p);
|
||||
break;
|
||||
case '22':
|
||||
video720p = videoUrls[key]['url'];
|
||||
console.log(video720p);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
@ -157,71 +148,119 @@ function playVideo(videoId) {
|
|||
}
|
||||
|
||||
if (!useEmbedPlayer) {
|
||||
videoHtml = '<video class="videoPlayer" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" src="' + defaultUrl + '" poster="' + videoThumbnail + '" autoplay>' + subtitleHtml + '</video>';
|
||||
videoHtml = '<video class="videoPlayer" type="application/x-mpegURL" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" src="' + defaultUrl + '" poster="' + videoThumbnail + '" autoplay>';
|
||||
|
||||
|
||||
if (typeof(info.player_response.captions) === 'object') {
|
||||
if (typeof(info.player_response.captions.playerCaptionsTracklistRenderer.captionTracks) === 'object') {
|
||||
const videoSubtitles = info.player_response.captions.playerCaptionsTracklistRenderer.captionTracks;
|
||||
|
||||
videoSubtitles.forEach((subtitle) => {
|
||||
let subtitleUrl = 'https://www.youtube.com/api/timedtext?lang=' + subtitle.languageCode + '&fmt=vtt&name=&v=' + videoId;
|
||||
|
||||
if (subtitle.kind == 'asr') {
|
||||
//subtitleUrl = subtitle.baseUrl;
|
||||
return;
|
||||
}
|
||||
|
||||
videoHtml = videoHtml + '<track kind="subtitles" src="' + subtitleUrl + '" srclang="' + subtitle.languageCode + '" label="' + subtitle.name.simpleText + '">';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
videoHtml = videoHtml + '</video>';
|
||||
}
|
||||
|
||||
const checkSubscription = isSubscribed(channelId);
|
||||
|
||||
// Change the subscribe button text depending on if the user has subscribed to the channel or not.
|
||||
|
||||
checkSubscription.then((results) => {
|
||||
const subscribeButton = document.getElementById('subscribeButton');
|
||||
|
||||
if (results === false) {
|
||||
subscribeText = 'SUBSCRIBE';
|
||||
if (subscribeButton != null) {
|
||||
subscribeButton.innerHTML = 'SUBSCRIBE';
|
||||
}
|
||||
} else {
|
||||
subscribeText = 'UNSUBSCRIBE';
|
||||
if (subscribeButton != null) {
|
||||
subscribeButton.innerHTML = 'UNSUBSCRIBE';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// API Request
|
||||
youtubeAPI('channels', {
|
||||
'id': channelId,
|
||||
'part': 'snippet'
|
||||
}, function(data) {
|
||||
const channelThumbnail = data['items'][0]['snippet']['thumbnails']['high']['url'];
|
||||
const playerTemplate = require('./templates/player.html')
|
||||
mustache.parse(playerTemplate);
|
||||
const rendered = mustache.render(playerTemplate, {
|
||||
videoHtml: videoHtml,
|
||||
videoQuality: defaultQuality,
|
||||
videoTitle: info['title'],
|
||||
videoViews: videoViews,
|
||||
videoThumbnail: videoThumbnail,
|
||||
channelName: info['author']['name'],
|
||||
videoLikes: videoLikes,
|
||||
videoDislikes: videoDislikes,
|
||||
likePercentage: likePercentage,
|
||||
videoId: videoId,
|
||||
channelId: channelId,
|
||||
channelIcon: channelThumbnail,
|
||||
publishedDate: publishedDate,
|
||||
description: description,
|
||||
isSubscribed: subscribeText,
|
||||
savedText: savedText,
|
||||
savedIconClass: savedIconClass,
|
||||
savedIconColor: savedIconColor,
|
||||
video480p: video480p,
|
||||
video720p: video720p,
|
||||
embedPlayer: embedPlayer,
|
||||
});
|
||||
|
||||
const playerTemplate = require('./templates/player.html')
|
||||
mustache.parse(playerTemplate);
|
||||
const rendered = mustache.render(playerTemplate, {
|
||||
videoHtml: videoHtml,
|
||||
videoQuality: defaultQuality,
|
||||
videoTitle: info['title'],
|
||||
videoViews: videoViews,
|
||||
videoThumbnail: videoThumbnail,
|
||||
channelName: info['uploader'],
|
||||
videoLikes: videoLikes,
|
||||
videoDislikes: videoDislikes,
|
||||
likePercentage: likePercentage,
|
||||
videoId: videoId,
|
||||
channelId: channelId,
|
||||
channelIcon: channelThumbnail,
|
||||
publishedDate: publishedDate,
|
||||
description: description,
|
||||
isSubscribed: subscribeText,
|
||||
savedText: savedText,
|
||||
savedIconClass: savedIconClass,
|
||||
savedIconColor: savedIconColor,
|
||||
video480p: video480p,
|
||||
video720p: video720p,
|
||||
embedPlayer: embedPlayer,
|
||||
// Add the video to the user's history
|
||||
addToHistory(videoId);
|
||||
|
||||
if (useWindowPlayer){
|
||||
// Create a new browser window.
|
||||
const BrowserWindow = electron.remote.BrowserWindow;
|
||||
|
||||
let newWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 700
|
||||
});
|
||||
|
||||
let playerWindowHeader = require('./templates/playerWindow.html');
|
||||
|
||||
mustache.parse(playerWindowHeader);
|
||||
const playerHeaderRender = mustache.render(playerWindowHeader, {
|
||||
videoId: videoId,
|
||||
channelId: channelId
|
||||
});
|
||||
|
||||
newWindow.loadURL('data:text/html;charset=UTF-8,' + encodeURIComponent(playerHeaderRender + rendered), {
|
||||
baseURLForDataURL: `file://${__dirname}/src`
|
||||
});
|
||||
}
|
||||
else{
|
||||
$('#main').html(rendered);
|
||||
stopLoadingAnimation();
|
||||
|
||||
if (Object.keys(info['subtitles']).length > 0) {
|
||||
showVideoRecommendations(videoId);
|
||||
|
||||
// Sometimes a video URL is found, but the video will not play. I believe the issue is
|
||||
// that the video has yet to render for that quality, as the video will be available at a later time.
|
||||
// This will check the URLs and switch video sources if there is an error.
|
||||
checkVideoUrls(video480p, video720p);
|
||||
|
||||
// Hide subtitles by default
|
||||
if (typeof(info['subtitles']) !== 'undefined' && Object.keys(info['subtitles']).length > 0) {
|
||||
let textTracks = $('.videoPlayer').get(0).textTracks;
|
||||
Object.keys(textTracks).forEach((track) => {
|
||||
textTracks[track].mode = 'hidden';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
showVideoRecommendations(videoId);
|
||||
console.log('done');
|
||||
});
|
||||
// Sometimes a video URL is found, but the video will not play. I believe the issue is
|
||||
// that the video has yet to render for that quality, as the video will be available at a later time.
|
||||
// This will check the URLs and switch video sources if there is an error.
|
||||
checkVideoUrls(video480p, video720p);
|
||||
// Add the video to the user's history
|
||||
addToHistory(videoId);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -51,11 +51,11 @@ function addSavedVideo(videoId){
|
|||
/**
|
||||
* Removes a video from the user's saved video database.
|
||||
*
|
||||
* @param {string} {videoId} - The video ID of the video that will be removed.
|
||||
* @param {string} videoId - The video ID of the video that will be removed.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function removeSavedVideo(videoId: string){
|
||||
function removeSavedVideo(videoId, string){
|
||||
savedVidsDb.remove({
|
||||
videoId: videoId
|
||||
}, {}, (err, numRemoved) => {
|
||||
|
|
|
@ -72,6 +72,12 @@ function showSettings() {
|
|||
} else {
|
||||
document.getElementById('themeSwitch').checked = true;
|
||||
}
|
||||
|
||||
if (useTor) {
|
||||
document.getElementById('torSwitch').checked = true;
|
||||
} else {
|
||||
document.getElementById('torSwitch').checked = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -84,46 +90,49 @@ function checkDefaultSettings() {
|
|||
|
||||
// Grab a random API Key.
|
||||
apiKey = apiKeyBank[Math.floor(Math.random() * apiKeyBank.length)];
|
||||
let newSetting;
|
||||
|
||||
// Check settings database
|
||||
settingsDb.find({}, (err, docs) => {
|
||||
if (jQuery.isEmptyObject(docs)) {
|
||||
let settingDefaults = {
|
||||
'theme': 'light',
|
||||
'apiKey': apiKey,
|
||||
'useTor': false
|
||||
};
|
||||
|
||||
// Set User Defaults
|
||||
let themeDefault = {
|
||||
_id: 'theme',
|
||||
value: 'light',
|
||||
};
|
||||
console.log(settingDefaults);
|
||||
|
||||
let apiDefault = {
|
||||
_id: 'apiKey',
|
||||
value: apiKey,
|
||||
};
|
||||
for (let key in settingDefaults){
|
||||
settingsDb.find({_id: key}, (err, docs) => {
|
||||
if (jQuery.isEmptyObject(docs)) {
|
||||
newSetting = {
|
||||
_id: key,
|
||||
value: settingDefaults[key]
|
||||
};
|
||||
|
||||
// Set default theme
|
||||
setTheme('light');
|
||||
settingsDb.insert(newSetting);
|
||||
|
||||
// Inset default settings into the settings database.
|
||||
settingsDb.insert(themeDefault);
|
||||
settingsDb.insert(apiDefault);
|
||||
} else {
|
||||
// Use user current defaults
|
||||
docs.forEach((setting) => {
|
||||
switch (setting['_id']) {
|
||||
if (key == 'theme'){
|
||||
setTheme('light');
|
||||
}
|
||||
}
|
||||
else{
|
||||
switch (docs[0]['_id']) {
|
||||
case 'theme':
|
||||
setTheme(setting['value']);
|
||||
setTheme(docs[0]['value']);
|
||||
break;
|
||||
case 'apiKey':
|
||||
if (apiKeyBank.indexOf(setting['value']) == -1) {
|
||||
apiKey = setting['value'];
|
||||
if (apiKeyBank.indexOf(docs[0]['value']) == -1) {
|
||||
apiKey = docs[0]['value'];
|
||||
}
|
||||
break;
|
||||
case 'useTor':
|
||||
useTor = docs[0]['value'];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -132,17 +141,21 @@ function checkDefaultSettings() {
|
|||
* @return {Void}
|
||||
*/
|
||||
function updateSettings() {
|
||||
var themeSwitch = document.getElementById('themeSwitch').checked;
|
||||
var key = document.getElementById('api-key').value;
|
||||
let themeSwitch = document.getElementById('themeSwitch').checked;
|
||||
let torSwitch = document.getElementById('torSwitch').checked;
|
||||
let key = document.getElementById('api-key').value;
|
||||
let theme = 'light';
|
||||
|
||||
apiKey = apiKeyBank[Math.floor(Math.random() * apiKeyBank.length)];
|
||||
|
||||
if (themeSwitch == true) {
|
||||
var theme = 'dark';
|
||||
} else {
|
||||
var theme = 'light';
|
||||
console.log(themeSwitch);
|
||||
|
||||
if (themeSwitch === true) {
|
||||
theme = 'dark';
|
||||
}
|
||||
|
||||
console.log(theme);
|
||||
|
||||
// Update default theme
|
||||
settingsDb.update({
|
||||
_id: 'theme'
|
||||
|
@ -153,6 +166,17 @@ function updateSettings() {
|
|||
console.log(numReplaced);
|
||||
});
|
||||
|
||||
// Update tor usage.
|
||||
settingsDb.update({
|
||||
_id: 'useTor'
|
||||
}, {
|
||||
value: torSwitch
|
||||
}, {}, function(err, numReplaced) {
|
||||
console.log(err);
|
||||
console.log(numReplaced);
|
||||
useTor = torSwitch;
|
||||
});
|
||||
|
||||
if (key != '') {
|
||||
settingsDb.update({
|
||||
_id: 'apiKey'
|
||||
|
@ -207,14 +231,20 @@ function setTheme(option) {
|
|||
// Grab the css file to be used.
|
||||
switch (option) {
|
||||
case 'light':
|
||||
cssFile = 'style/lightTheme.css';
|
||||
cssFile = './style/lightTheme.css';
|
||||
document.getElementById('menuText').src = 'icons/textBlack.png';
|
||||
document.getElementById('menuIcon').src = 'icons/iconBlack.png';
|
||||
document.getElementById('menuButton').style.color = 'black';
|
||||
break;
|
||||
case 'dark':
|
||||
cssFile = 'style/darkTheme.css';
|
||||
cssFile = './style/darkTheme.css';
|
||||
document.getElementById('menuText').src = 'icons/textColor.png';
|
||||
document.getElementById('menuIcon').src = 'icons/iconColor.png';
|
||||
document.getElementById('menuButton').style.color = 'white';
|
||||
break;
|
||||
default:
|
||||
// Default to the light theme
|
||||
cssFile = 'style/lightTheme.css';
|
||||
cssFile = './style/lightTheme.css';
|
||||
break;
|
||||
}
|
||||
newTheme.setAttribute("href", cssFile);
|
||||
|
|
|
@ -79,6 +79,7 @@ function removeSubscription(channelId) {
|
|||
*/
|
||||
function loadSubscriptions() {
|
||||
clearMainContainer();
|
||||
showToast('Getting Subscriptions. Please wait...');
|
||||
const loading = document.getElementById('loading');
|
||||
|
||||
startLoadingAnimation()
|
||||
|
@ -162,90 +163,6 @@ function loadSubscriptions() {
|
|||
container.innerHTML = `<h2 class="message">Your Subscription list is currently empty. Start adding subscriptions
|
||||
to see them here.<br /><br /><i class="far fa-frown" style="font-size: 200px"></i></h2>`;
|
||||
}
|
||||
|
||||
|
||||
// Yes, This function is the thing that needs to most improvment
|
||||
/*
|
||||
if (results.length > 0) {
|
||||
showToast('Getting Subscriptions. This may take a while...');
|
||||
|
||||
/*
|
||||
* If this loop gets rewritten, we can remove the asyncLoop dependency from the project.
|
||||
*
|
||||
* I wasn't able to figure out a way to loop through the list of channels and grab the recent uploads
|
||||
* while then sorting them afterwards, this was my best solution at the time. I'm sure someone more
|
||||
* experienced in Node can help out with this.
|
||||
*/
|
||||
/*
|
||||
asyncLoop(results, (sub, next) => {
|
||||
const channelId = sub['channelId'];
|
||||
|
||||
/*
|
||||
* Grab the channels 15 most recent uploads. Typically this should be enough.
|
||||
* This number can be changed if we feel necessary.
|
||||
*/
|
||||
/*
|
||||
youtubeAPI('search', {
|
||||
part: 'snippet',
|
||||
channelId: channelId,
|
||||
type: 'video',
|
||||
maxResults: 15,
|
||||
order: 'date',
|
||||
}, (data) => {
|
||||
videoList = videoList.concat(data.items);
|
||||
|
||||
next();
|
||||
});
|
||||
}, (err) => {
|
||||
// Sort the videos by date
|
||||
videoList.sort((a, b) => {
|
||||
const date1 = Date.parse(a.snippet.publishedAt);
|
||||
const date2 = Date.parse(b.snippet.publishedAt);
|
||||
|
||||
return date2.valueOf() - date1.valueOf();
|
||||
});
|
||||
|
||||
// Render the videos to the application.
|
||||
createVideoListContainer('Latest Subscriptions:');
|
||||
|
||||
// The YouTube website limits the subscriptions to 100 before grabbing more so we only show 100
|
||||
// to keep the app running at a good speed.
|
||||
if(videoList.length < 50){
|
||||
let grabDuration = getDuration(videoList.slice(0,49));
|
||||
|
||||
grabDuration.then((list) => {
|
||||
list.items.forEach((video) => {
|
||||
displayVideo(video);
|
||||
});
|
||||
});
|
||||
}
|
||||
else{
|
||||
let finishedList = []
|
||||
let firstBatchDuration = getDuration(videoList.slice(0, 49));
|
||||
|
||||
firstBatchDuration.then((list1) => {
|
||||
finishedList = finishedList.concat(list1.items);
|
||||
let secondBatchDuration = getDuration(videoList.slice(50, 99));
|
||||
|
||||
secondBatchDuration.then((list2) => {
|
||||
finishedList = finishedList.concat(list2.items);
|
||||
finishedList.forEach((video) => {
|
||||
displayVideo(video);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
stopLoadingAnimation()
|
||||
});
|
||||
} else {
|
||||
// User has no subscriptions. Display message.
|
||||
const container = document.getElementById('main');
|
||||
stopLoadingAnimation()
|
||||
|
||||
container.innerHTML = `<h2 class="message">Your Subscription list is currently empty. Start adding subscriptions
|
||||
to see them here.<br /><br /><i class="far fa-frown" style="font-size: 200px"></i></h2>`;
|
||||
}
|
||||
});*/
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
This file is part of FreeTube.
|
||||
|
||||
FreeTube is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
FreeTube is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* A file for checking / managing updates
|
||||
*/
|
||||
|
||||
const updateChecker = require('github-version-checker');
|
||||
|
||||
const options = {
|
||||
token: 'USERACCESSTOKEN', // personal access token. Github will not allow commiting the access token, which is why this is blank.
|
||||
repo: 'freetube', // repository name
|
||||
owner: 'freetubeapp', // repository owner
|
||||
currentVersion: require('electron').remote.app.getVersion(), // your app's current version
|
||||
fetchTags: false // whether to fetch releases or tags
|
||||
};
|
||||
|
||||
const openReleasePage = function(){
|
||||
shell.openExternal('https://github.com/FreeTubeApp/FreeTube/releases');
|
||||
}
|
||||
|
||||
/*function checkForUpdates() {
|
||||
updateChecker(options, function(error, update) { // callback function
|
||||
if (error){
|
||||
showToast('There was a problem with checking for updates');
|
||||
console.log(error);
|
||||
}
|
||||
if (update) { // print some update info if an update is available
|
||||
confirmFunction(update.name + ' is now available! Would you like to download the update?', openReleasePage);
|
||||
}
|
||||
else{
|
||||
showToast('No update is currently available.');
|
||||
}
|
||||
});
|
||||
}*/
|
||||
|
||||
updateChecker(options, function(error, update) { // callback function
|
||||
if (error) throw error;
|
||||
if (update) { // print some update info if an update is available
|
||||
confirmFunction(update.name + ' is now available! Would you like to download the update?', openReleasePage);
|
||||
}
|
||||
});
|
347
src/js/videos.js
|
@ -18,12 +18,12 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
||||
|
||||
/**
|
||||
* Perform a search using the YouTube API. The search query is grabbed from the #search element.
|
||||
*
|
||||
* @param {string} nextPageToken - Optional: The page token to be inlcuded in the search.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
* Perform a search using the YouTube API. The search query is grabbed from the #search element.
|
||||
*
|
||||
* @param {string} nextPageToken - Optional: The page token to be inlcuded in the search.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function search(nextPageToken = '') {
|
||||
const query = document.getElementById('search').value;
|
||||
|
||||
|
@ -42,13 +42,45 @@ function search(nextPageToken = '') {
|
|||
youtubeAPI('search', {
|
||||
q: query,
|
||||
part: 'id',
|
||||
type: 'video',
|
||||
pageToken: nextPageToken,
|
||||
maxResults: 25,
|
||||
}, function (data){
|
||||
let grabDuration = getDuration(data.items);
|
||||
}, function(data) {
|
||||
console.log(data);
|
||||
|
||||
let channels = data.items.filter((item) => {
|
||||
if (item.id.kind === 'youtube#channel') {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
let playlists = data.items.filter((item) => {
|
||||
if (item.id.kind === 'youtube#playlist') {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
let videos = data.items.filter((item) => {
|
||||
if (item.id.kind === 'youtube#video') {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
console.log(channels);
|
||||
console.log(typeof(channels));
|
||||
console.log(playlists);
|
||||
|
||||
if(playlists.length > 0){
|
||||
//displayPlaylists(playlists);
|
||||
}
|
||||
|
||||
if(channels.length > 0){
|
||||
displayChannels(channels);
|
||||
}
|
||||
|
||||
let grabDuration = getDuration(videos);
|
||||
|
||||
grabDuration.then((videoList) => {
|
||||
console.log(videoList);
|
||||
videoList.items.forEach(displayVideo);
|
||||
});
|
||||
|
||||
|
@ -61,30 +93,27 @@ function search(nextPageToken = '') {
|
|||
}
|
||||
|
||||
/**
|
||||
* Grab the duration of the videos
|
||||
*
|
||||
* @param {array} data - An array of videos to get the duration from
|
||||
*
|
||||
* @return {promise} - The list of videos with the duration included.
|
||||
*/
|
||||
function getDuration(data){
|
||||
* Grab the duration of the videos
|
||||
*
|
||||
* @param {array} data - An array of videos to get the duration from
|
||||
*
|
||||
* @return {promise} - The list of videos with the duration included.
|
||||
*/
|
||||
function getDuration(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let videoIdList = '';
|
||||
|
||||
for(let i = 0; i < data.length; i++){
|
||||
if (videoIdList === ''){
|
||||
if(typeof(data[i]['id']) === 'string'){
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (videoIdList === '') {
|
||||
if (typeof(data[i]['id']) === 'string') {
|
||||
videoIdList = data[i]['id'];
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
videoIdList = data[i]['id']['videoId'];
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(typeof(data[i]['id']) === 'string'){
|
||||
} else {
|
||||
if (typeof(data[i]['id']) === 'string') {
|
||||
videoIdList = videoIdList + ', ' + data[i]['id'];
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
videoIdList = videoIdList + ', ' + data[i]['id']['videoId'];
|
||||
}
|
||||
}
|
||||
|
@ -100,14 +129,14 @@ function getDuration(data){
|
|||
}
|
||||
|
||||
/**
|
||||
* Display a video on the page. Function is typically contained in a loop.
|
||||
*
|
||||
* @param {video} video - The video ID of the video to be displayed.
|
||||
* @param {string} listType - Optional: Specifies the list type of the video
|
||||
* Used for displaying the remove icon for history and saved videos.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
* Display a video on the page. Function is typically contained in a loop.
|
||||
*
|
||||
* @param {video} video - The video ID of the video to be displayed.
|
||||
* @param {string} listType - Optional: Specifies the list type of the video
|
||||
* Used for displaying the remove icon for history and saved videos.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function displayVideo(video, listType = '') {
|
||||
const videoSnippet = video.snippet;
|
||||
|
||||
|
@ -132,18 +161,18 @@ function displayVideo(video, listType = '') {
|
|||
};
|
||||
|
||||
// Includes text if the video is live.
|
||||
const liveText = (videoSnippet.liveBroadcastContent === 'live')? 'LIVE NOW' : '';
|
||||
const liveText = (videoSnippet.liveBroadcastContent === 'live') ? 'LIVE NOW' : '';
|
||||
const videoListTemplate = require('./templates/videoList.html');
|
||||
|
||||
mustache.parse(videoListTemplate);
|
||||
const rendered = mustache.render(videoListTemplate, {
|
||||
videoId: videoId,
|
||||
videoThumbnail: videoSnippet.thumbnails.medium.url,
|
||||
videoTitle: videoSnippet.title,
|
||||
channelName: videoSnippet.channelTitle,
|
||||
videoThumbnail: videoSnippet.thumbnails.medium.url,
|
||||
videoTitle: videoSnippet.title,
|
||||
channelName: videoSnippet.channelTitle,
|
||||
videoDescription: videoSnippet.description,
|
||||
channelId: videoSnippet.channelId,
|
||||
videoDuration: videoDuration,
|
||||
channelId: videoSnippet.channelId,
|
||||
videoDuration: videoDuration,
|
||||
publishedDate: publishedDate,
|
||||
liveText: liveText,
|
||||
deleteHtml: deleteHtml,
|
||||
|
@ -158,13 +187,95 @@ function displayVideo(video, listType = '') {
|
|||
}
|
||||
}
|
||||
|
||||
function displayChannels(channels) {
|
||||
let channelIds;
|
||||
|
||||
channels.forEach((channel) => {
|
||||
if (typeof(channelIds) === 'undefined') {
|
||||
channelIds = channel.id.channelId;
|
||||
} else {
|
||||
channelIds = channelIds + ',' + channel.id.channelId;
|
||||
}
|
||||
});
|
||||
|
||||
console.log(channelIds);
|
||||
|
||||
youtubeAPI('channels', {
|
||||
part: 'snippet,statistics',
|
||||
id: channelIds,
|
||||
}, function(data) {
|
||||
console.log(data);
|
||||
let items = data['items'].reverse();
|
||||
const videoListTemplate = require('./templates/channelList.html');
|
||||
|
||||
console.log(items);
|
||||
|
||||
items.forEach((item) => {
|
||||
mustache.parse(videoListTemplate);
|
||||
let rendered = mustache.render(videoListTemplate, {
|
||||
channelId: item.id,
|
||||
channelThumbnail: item.snippet.thumbnails.medium.url,
|
||||
channelName: item.snippet.title,
|
||||
channelDescription: item.snippet.description,
|
||||
subscriberCount: item.statistics.subscriberCount,
|
||||
videoCount: item.statistics.videoCount,
|
||||
});
|
||||
|
||||
$(rendered).insertBefore('#getNextPage');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function displayPlaylists(playlists) {
|
||||
let playlistIds;
|
||||
|
||||
playlists.forEach((playlist) => {
|
||||
if (typeof(playlistIds) === 'undefined') {
|
||||
playlistIds = playlist.id.playlistId;
|
||||
} else {
|
||||
playlistIds = playlistIds + ',' + playlist.id.playlistId;
|
||||
}
|
||||
});
|
||||
|
||||
console.log(playlistIds);
|
||||
|
||||
youtubeAPI('playlists', {
|
||||
part: 'snippet,contentDetails',
|
||||
id: playlistIds,
|
||||
}, function(data) {
|
||||
console.log(data);
|
||||
let items = data['items'].reverse();
|
||||
const playlistListTemplate = require('./templates/playlistList.html');
|
||||
|
||||
console.log(items);
|
||||
|
||||
items.forEach((item) => {
|
||||
let dateString = new Date(item.snippet.publishedAt);
|
||||
let publishedDate = dateFormat(dateString, "mmm dS, yyyy");
|
||||
|
||||
mustache.parse(playlistListTemplate);
|
||||
let rendered = mustache.render(playlistListTemplate, {
|
||||
channelId: item.snippet.channelId,
|
||||
channelName: item.snippet.channelTitle,
|
||||
playlistThumbnail: item.snippet.thumbnails.medium.url,
|
||||
playlistTitle: item.snippet.title,
|
||||
playlistDescription: item.snippet.description,
|
||||
videoCount: item.contentDetails.itemCount,
|
||||
publishedDate: publishedDate,
|
||||
});
|
||||
|
||||
$(rendered).insertBefore('#getNextPage');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the page token to the next page button during a video search.
|
||||
*
|
||||
* @param {string} nextPageToken - The page token to replace the button function.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
* Changes the page token to the next page button during a video search.
|
||||
*
|
||||
* @param {string} nextPageToken - The page token to replace the button function.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function addNextPage(nextPageToken) {
|
||||
let oldFetchButton = document.getElementById('getNextPage');
|
||||
|
||||
|
@ -185,19 +296,19 @@ function addNextPage(nextPageToken) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Grab the video recommendations for a video. This does not get recommendations based on what you watch,
|
||||
* as that would defeat the main purpose of using FreeTube. At any time you can check the video on HookTube
|
||||
* and compare the recommendations there. They should be nearly identical.
|
||||
*
|
||||
* @param {string} videoId - The video ID of the video to get recommendations from.
|
||||
*/
|
||||
* Grab the video recommendations for a video. This does not get recommendations based on what you watch,
|
||||
* as that would defeat the main purpose of using FreeTube. At any time you can check the video on HookTube
|
||||
* and compare the recommendations there. They should be nearly identical.
|
||||
*
|
||||
* @param {string} videoId - The video ID of the video to get recommendations from.
|
||||
*/
|
||||
function showVideoRecommendations(videoId) {
|
||||
youtubeAPI('search', {
|
||||
part: 'id',
|
||||
type: 'video',
|
||||
relatedToVideoId: videoId,
|
||||
maxResults: 15,
|
||||
}, function (data){
|
||||
}, function(data) {
|
||||
let grabDuration = getDuration(data.items);
|
||||
grabDuration.then((videoList) => {
|
||||
videoList.items.forEach((video) => {
|
||||
|
@ -208,10 +319,10 @@ function showVideoRecommendations(videoId) {
|
|||
mustache.parse(recommTemplate);
|
||||
const rendered = mustache.render(recommTemplate, {
|
||||
videoId: video.id,
|
||||
videoTitle: snippet.title,
|
||||
channelName: snippet.channelTitle,
|
||||
videoTitle: snippet.title,
|
||||
channelName: snippet.channelTitle,
|
||||
videoThumbnail: snippet.thumbnails.medium.url,
|
||||
videoDuration: videoDuration,
|
||||
videoDuration: videoDuration,
|
||||
publishedDate: dateFormat(snippet.publishedAt, "mmm dS, yyyy")
|
||||
});
|
||||
const recommendationHtml = $('#recommendations').html();
|
||||
|
@ -222,13 +333,20 @@ function showVideoRecommendations(videoId) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check if a link is a valid YouTube/HookTube link and play that video. Gets input
|
||||
* from the #jumpToInput element.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function parseVideoLink() {
|
||||
let input = document.getElementById('jumpToInput').value;
|
||||
* Check if a link is a valid YouTube/HookTube link and play that video. Gets input
|
||||
* from the #jumpToInput element.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function parseSearchText(url = '') {
|
||||
let input;
|
||||
|
||||
if (url === ''){
|
||||
input = document.getElementById('search').value;
|
||||
}
|
||||
else{
|
||||
input = url;
|
||||
}
|
||||
|
||||
if (input === '') {
|
||||
return;
|
||||
|
@ -243,26 +361,28 @@ function parseVideoLink() {
|
|||
|
||||
// Play video if a match is found.
|
||||
try {
|
||||
console.log('Match Found');
|
||||
playVideo(match[2]);
|
||||
} catch (err) {
|
||||
showToast('Video Not Found');
|
||||
console.log('Video not found');
|
||||
search();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert duration into a more readable format
|
||||
*
|
||||
* @param {string} durationString - The string containing the video duration. Formated as 'PT12H34M56S'
|
||||
*
|
||||
* @return {string} - The formated string. Ex: 12:34:56
|
||||
*/
|
||||
function parseVideoDuration(durationString){
|
||||
* Convert duration into a more readable format
|
||||
*
|
||||
* @param {string} durationString - The string containing the video duration. Formated as 'PT12H34M56S'
|
||||
*
|
||||
* @return {string} - The formated string. Ex: 12:34:56
|
||||
*/
|
||||
function parseVideoDuration(durationString) {
|
||||
let match = durationString.match(/PT(\d+H)?(\d+M)?(\d+S)?/);
|
||||
let duration = '';
|
||||
|
||||
match = match.slice(1).map(function(x) {
|
||||
if (x != null) {
|
||||
return x.replace(/\D/, '');
|
||||
return x.replace(/\D/, '');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -270,30 +390,25 @@ function parseVideoDuration(durationString){
|
|||
let minutes = (parseInt(match[1]) || 0);
|
||||
let seconds = (parseInt(match[2]) || 0);
|
||||
|
||||
if (hours != 0){
|
||||
if (hours != 0) {
|
||||
duration = hours + ':';
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
duration = minutes + ':';
|
||||
}
|
||||
|
||||
if (hours != 0 && minutes < 10){
|
||||
if (hours != 0 && minutes < 10) {
|
||||
duration = duration + '0' + minutes + ':';
|
||||
}
|
||||
else if (hours != 0 && minutes > 10){
|
||||
} else if (hours != 0 && minutes > 10) {
|
||||
duration = duration + minutes + ':';
|
||||
}
|
||||
else if (hours != 0 && minutes == 0){
|
||||
} else if (hours != 0 && minutes == 0) {
|
||||
duration = duration + '00:';
|
||||
}
|
||||
|
||||
if (seconds == 0){
|
||||
if (seconds == 0) {
|
||||
duration = duration + '00';
|
||||
}
|
||||
else if (seconds < 10){
|
||||
} else if (seconds < 10) {
|
||||
duration = duration + '0' + seconds;
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
duration = duration + seconds;
|
||||
}
|
||||
|
||||
|
@ -301,10 +416,10 @@ function parseVideoDuration(durationString){
|
|||
}
|
||||
|
||||
/**
|
||||
* Grab the most popular videos over the last couple of days and display them.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
* Grab the most popular videos over the last couple of days and display them.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function showMostPopular() {
|
||||
clearMainContainer();
|
||||
startLoadingAnimation();
|
||||
|
@ -323,7 +438,7 @@ function showMostPopular() {
|
|||
type: 'video',
|
||||
publishedAfter: d.toISOString(),
|
||||
maxResults: 50,
|
||||
}, function (data){
|
||||
}, function(data) {
|
||||
createVideoListContainer('Most Popular:');
|
||||
console.log(data);
|
||||
let grabDuration = getDuration(data.items);
|
||||
|
@ -337,13 +452,13 @@ function showMostPopular() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a link of the video to HookTube or YouTube and copy it to the user's clipboard.
|
||||
*
|
||||
* @param {string} website - The website to watch the video on.
|
||||
* @param {string} videoId - The video ID of the video to add to the URL
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
* Create a link of the video to HookTube or YouTube and copy it to the user's clipboard.
|
||||
*
|
||||
* @param {string} website - The website to watch the video on.
|
||||
* @param {string} videoId - The video ID of the video to add to the URL
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function copyLink(website, videoId) {
|
||||
// Create the URL and copy to the clipboard.
|
||||
const url = 'https://' + website + '.com/watch?v=' + videoId;
|
||||
|
@ -352,19 +467,19 @@ function copyLink(website, videoId) {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
* 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 getChannelAndPlayer(videoId) {
|
||||
console.log(videoId);
|
||||
return new Promise((resolve, reject) => {
|
||||
youtubeAPI('videos', {
|
||||
part: 'snippet,player',
|
||||
id: videoId,
|
||||
}, function (data){
|
||||
}, function(data) {
|
||||
let embedHtml = data.items[0].player.embedHtml;
|
||||
embedHtml = embedHtml.replace('src="', 'src="https:');
|
||||
embedHtml = embedHtml.replace('width="480px"', '');
|
||||
|
@ -376,20 +491,20 @@ function getChannelAndPlayer(videoId) {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* is why this check is needed. The video URL will typically be resolved over time.
|
||||
*
|
||||
* @param {string} video480p - The URL to the 480p video.
|
||||
* @param {string} video720p - The URL to the 720p video.
|
||||
*/
|
||||
* 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
|
||||
* is why this check is needed. The video URL will typically be resolved over time.
|
||||
*
|
||||
* @param {string} video480p - The URL to the 480p video.
|
||||
* @param {string} video720p - The URL to the 720p video.
|
||||
*/
|
||||
function checkVideoUrls(video480p, video720p) {
|
||||
const currentQuality = $('#currentQuality').html();
|
||||
let buttonEmbed = document.getElementById('qualityEmbed');
|
||||
|
||||
let valid480 = false;
|
||||
|
||||
if (typeof(video480p) !== 'undefined'){
|
||||
if (typeof(video480p) !== 'undefined') {
|
||||
let get480pUrl = fetch(video480p);
|
||||
get480pUrl.then((status) => {
|
||||
switch (status.status) {
|
||||
|
@ -412,7 +527,7 @@ function checkVideoUrls(video480p, video720p) {
|
|||
break;
|
||||
default:
|
||||
console.log('480p is valid');
|
||||
if (currentQuality === '720p' && typeof(video720p) === 'undefined'){
|
||||
if (currentQuality === '720p' && typeof(video720p) === 'undefined') {
|
||||
changeQuality(video480p);
|
||||
}
|
||||
break;
|
||||
|
@ -420,7 +535,7 @@ function checkVideoUrls(video480p, video720p) {
|
|||
});
|
||||
}
|
||||
|
||||
if (typeof(video720p) !== 'undefined'){
|
||||
if (typeof(video720p) !== 'undefined') {
|
||||
let get720pUrl = fetch(video720p);
|
||||
get720pUrl.then((status) => {
|
||||
switch (status.status) {
|
||||
|
@ -430,7 +545,7 @@ function checkVideoUrls(video480p, video720p) {
|
|||
$(document).on('click', '#quality720p', (event) => {
|
||||
changeQuality('');
|
||||
});
|
||||
if (typeof(valid480) !== 'undefined'){
|
||||
if (typeof(valid480) !== 'undefined') {
|
||||
changeQuality(video480p, '480p');
|
||||
}
|
||||
break;
|
||||
|
@ -444,7 +559,7 @@ function checkVideoUrls(video480p, video720p) {
|
|||
break;
|
||||
default:
|
||||
console.log('720p is valid');
|
||||
if (currentQuality === '720p'){
|
||||
if (currentQuality === '720p') {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -7,48 +7,62 @@
|
|||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
|
||||
function youtubeAPI(resource, params, success) {
|
||||
params.key = apiKey;
|
||||
$.getJSON(
|
||||
'https://www.googleapis.com/youtube/v3/' + resource,
|
||||
params,
|
||||
success
|
||||
).fail((xhr, textStatus, error) => {
|
||||
showToast('There was an error calling the YouTube API.');
|
||||
console.log(error);
|
||||
console.log(xhr);
|
||||
console.log(textStatus);
|
||||
stopLoadingAnimation();
|
||||
});
|
||||
|
||||
if (useTor) {
|
||||
tor.request('https://www.googleapis.com/youtube/v3/' + resource + '?' + $.param(params), function(err, res, body) {
|
||||
if (!err && res.statusCode == 200) {
|
||||
success(JSON.parse(body));
|
||||
} else {
|
||||
showToast('Unable to connect to the Tor network. Check the help page if you\'re having trouble setting up your node.');
|
||||
console.log(err);
|
||||
console.log(res);
|
||||
console.log(body);
|
||||
stopLoadingAnimation();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$.getJSON(
|
||||
'https://www.googleapis.com/youtube/v3/' + resource,
|
||||
params,
|
||||
success
|
||||
).fail((xhr, textStatus, error) => {
|
||||
showToast('There was an error calling the YouTube API.');
|
||||
console.log(error);
|
||||
console.log(xhr);
|
||||
console.log(textStatus);
|
||||
stopLoadingAnimation();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Use youtube-dl to resolve a video.
|
||||
*
|
||||
* @param {string} videoId - The video Id to get info from.
|
||||
* @param {function} callback - The function called on success with the info.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
* Use youtube-dl to resolve a video.
|
||||
*
|
||||
* @param {string} videoId - The video Id to get info from.
|
||||
* @param {function} callback - The function called on success with the info.
|
||||
*
|
||||
* @return {Void}
|
||||
*/
|
||||
function youtubedlGetInfo(videoId, callback) {
|
||||
let url = 'https://stormy-inlet-41826.herokuapp.com/api/info?url=https://www.youtube.com/watch?v=' + videoId + 'flatten=True&writesubtitles=True&geo_bypass=True';
|
||||
$.getJSON(url, (response) => {
|
||||
callback(response.info);
|
||||
});
|
||||
|
||||
let url = 'https://youtube.com/watch?v=' + videoId;
|
||||
let options = ['--all-subs', '--write-subs'];
|
||||
|
||||
/*let url = 'https://youtube.com/watch?v=' + videoId;
|
||||
let options = ['--all-subs'];
|
||||
|
||||
youtubedl.getInfo(url, options, function(err, info) {
|
||||
if (err){
|
||||
showToast('There was an issue calling youtube-dl.');
|
||||
ytdl.getInfo(url, options, function(err, info) {
|
||||
if (err) {
|
||||
showToast(err.message);
|
||||
stopLoadingAnimation();
|
||||
console.log(err);
|
||||
console.log(info);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Success');
|
||||
callback(info);
|
||||
});*/
|
||||
});
|
||||
}
|
||||
|
|
|
@ -46,35 +46,46 @@ a {
|
|||
line-height: normal;
|
||||
}
|
||||
|
||||
.menuButton {
|
||||
#menuButton {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
margin-left: 20px;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
#menuIcon {
|
||||
width: 25px;
|
||||
position: relative;
|
||||
top: 15px;
|
||||
right: 120px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
#menuText {
|
||||
width: 100px;
|
||||
position: relative;
|
||||
top: 5px;
|
||||
left: 10px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.searchBar {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
right: 135px;
|
||||
top: 0;
|
||||
width: 60%;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.searchBar input {
|
||||
width: 40%;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.searchButton {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.jumpToInput {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
#sideNav {
|
||||
height: 100vh;
|
||||
width: 250px;
|
||||
|
@ -246,6 +257,7 @@ a {
|
|||
font-weight: bold;
|
||||
line-height: 65px;
|
||||
visibility: hidden;
|
||||
z-index: 1;
|
||||
-webkit-box-shadow: 5px 5px 15px -5px rgba(0,0,0,0.75);
|
||||
-moz-box-shadow: 5px 5px 15px -5px rgba(0,0,0,0.75);
|
||||
box-shadow: 5px 5px 15px -5px rgba(0,0,0,0.75);
|
||||
|
|
|
@ -27,6 +27,21 @@ iframe {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.channelThumbnail {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
left: 60px;
|
||||
}
|
||||
|
||||
.channelThumbnail img{
|
||||
width: 100%;
|
||||
border-radius: 200px 200px 200px 200px;
|
||||
-webkit-border-radius: 200px 200px 200px 200px;
|
||||
}
|
||||
|
||||
.videoOptions {
|
||||
float: right;
|
||||
width: 50px;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<div class='center'>
|
||||
<h1>FreeTube {{versionNumber}} Beta</h1>
|
||||
<img src='icons/logoColor.png' width='500px;'/>
|
||||
<h1>{{versionNumber}} Beta</h1>
|
||||
<h2>This software is FOSS and released under the <a href='https://www.gnu.org/licenses/quick-guide-gplv3.html'>GNU Public License v3+</a>.</h2>
|
||||
Found a bug? Want to suggest a feature? Want to help out? Check out our <a href='https://github.com/FreeTubeApp/FreeTube'>GitHub</a> page. Pull requests are welcome.
|
||||
<p>Thank you very much to the <a href='https://github.com/FreeTubeApp/FreeTube/wiki/Credits'>People and Projects</a> that make FreeTube possible!</p>
|
||||
<h3><i class='fas fa-envelope'></i> FreeTubeApp@protonmail.com</h3>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<div class='video'>
|
||||
<div class='channelThumbnail'>
|
||||
<img onclick='goToChannel("{{channelId}}")' src={{channelThumbnail}} />
|
||||
</div>
|
||||
<p onclick='goToChannel("{{channelId}}")' class='videoTitle'>{{channelName}}</p>
|
||||
<p onclick='goToChannel("{{channelId}}")' class='channelName'>{{subscriberCount}} subscribers - {{videoCount}} videos</p>
|
||||
<p onclick='goToChannel("{{channelId}}")' class='videoDescription'>{{channelDescription}}</p>
|
||||
</div>
|
||||
<hr />
|
|
@ -43,7 +43,7 @@
|
|||
COPY HOOKTUBE LINK
|
||||
</div>
|
||||
<a href='https://hooktube.com/watch?v={{videoId}}'>
|
||||
<div class='smallButton' onclick='copyLink("hooktube", "{{videoId}}")'>
|
||||
<div class='smallButton'>
|
||||
OPEN IN HOOKTUBE
|
||||
</div>
|
||||
</a>
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="style/main.css">
|
||||
<link rel="stylesheet" href="style/lightTheme.css">
|
||||
<link rel="stylesheet" href="style/player.css">
|
||||
<link rel="stylesheet" href="style/videoList.css">
|
||||
<link rel="stylesheet" href="style/channel.css">
|
||||
<link rel="stylesheet" href="style/loading.css">
|
||||
<link rel="stylesheet" href="style/fa-solid.min.css">
|
||||
<link rel="stylesheet" href="style/fontawesome-all.min.css">
|
||||
<script src="js/youtubeApi.js"></script>
|
||||
<script src="js/settings.js"></script>
|
||||
<script src="js/layout.js"></script>
|
||||
<script src="js/videos.js"></script>
|
||||
<script src="js/player.js"></script>
|
||||
<script src="js/subscriptions.js"></script>
|
||||
<script src="js/channels.js"></script>
|
||||
<script src="js/savedVideos.js"></script>
|
||||
<script src="js/history.js"></script>
|
||||
<script src="js/events.js"></script>
|
||||
<script>
|
||||
showVideoRecommendations("{{videoId}}");
|
||||
|
||||
const checkIfSubscribed = isSubscribed('{{channelId}}');
|
||||
|
||||
checkIfSubscribed.then((results) => {
|
||||
|
||||
const subscribeButton = document.getElementById('subscribeButton');
|
||||
console.log(results);
|
||||
console.log(subscribeButton);
|
||||
|
||||
if (results === false) {
|
||||
if (subscribeButton != null) {
|
||||
subscribeButton.innerHTML = 'UNSUBSCRIBE';
|
||||
}
|
||||
} else {
|
||||
if (subscribeButton != null) {
|
||||
subscribeButton.innerHTML = 'SUBSCRIBE';
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<title>Freetube Player</title>
|
||||
<style>
|
||||
#main{
|
||||
width: 100%;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
position: relative;
|
||||
margin: 0px auto;
|
||||
}
|
||||
|
||||
#loading{
|
||||
width: 100%;
|
||||
height: 0%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id='loading'>
|
||||
<div class="spinner">
|
||||
<div class="double-bounce1"></div>
|
||||
<div class="double-bounce2"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id='confirmFunction'>
|
||||
<span id='confirmMessage'>Would you like to perform the function?</span>
|
||||
<div class='confirmButton' id='confirmYes'>Yes</div>
|
||||
<div class='confirmButton' id='confirmNo'>No</div>
|
||||
</div>
|
||||
<div id='toast'>
|
||||
<span id='toastMessage'></span>
|
||||
<i onclick='hideToast()' class="closeToast fas fa-times"></i>
|
||||
</div>
|
||||
<div class="topNav">
|
||||
<i onclick='toggleSideNavigation()' class="fas fa-bars" id='menuButton' style='display: none;'></i>
|
||||
<div class="searchBar">
|
||||
<input id='search' class="search" type="text" placeholder="Search / Go to URL">
|
||||
<i onclick='parseSearchText()' class="fas fa-search searchButton" style='margin-right: -10px; cursor: pointer'></i>
|
||||
</div>
|
||||
<img src='icons/iconBlack.png' id='menuIcon'/>
|
||||
|
||||
<img src='icons/textBlack.png' id='menuText'/>
|
||||
</div>
|
||||
<div id='main'>
|
|
@ -0,0 +1,10 @@
|
|||
<div class='video'>
|
||||
<div class='videoThumbnail'>
|
||||
<img onclick='console.log("Not Implemented")' src={{playlistThumbnail}} />
|
||||
</div>
|
||||
<p onclick='goToChannel("{{channelId}}")' class='videoTitle'>{{playlistTitle}}</p>
|
||||
<p onclick='goToChannel("{{channelId}}")' class='channelName'>{{channelName}} - {{publishedDate}}</p>
|
||||
<p onclick='goToChannel("{{channelId}}")' class='videoDescription'>{{playlistDescription}}</p>
|
||||
<p>VIEW FULL PLAYLIST ({{videoCount}} videos)</p>
|
||||
</div>
|
||||
<hr />
|
|
@ -6,6 +6,8 @@
|
|||
<br />
|
||||
<input type="checkbox" id="themeSwitch" name="set-name" class="switch-input" onchange='toggleTheme(this)' {{isChecked}}>
|
||||
<label for="themeSwitch" class="switch-label">Use Dark Theme</label>
|
||||
<input type="checkbox" id="torSwitch" name="set-name" class="switch-input" {{isChecked}}>
|
||||
<label for="torSwitch" class="switch-label">Use TOR for API calls</label>
|
||||
</div>
|
||||
<div class='center'>
|
||||
<div onclick='importSubscriptions()' class='settingsButton'>
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<video class="videoPlayer" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" poster="{{videoThumbnail}}" autoplay>
|
||||
<source src="{{videoUrl}}" type="video/mp4">
|
||||
{{subtitleElements}}
|
||||
</video>
|
|
@ -2,14 +2,15 @@
|
|||
<div class='videoOptions'>
|
||||
<i class="fas fa-ellipsis-v" onclick='showVideoOptions(this)'></i>
|
||||
<ul>
|
||||
<a href='https://youtube.com/watch?v={{videoId}}' ><li>Open in YouTube</li></a>
|
||||
<a href='https://hooktube.com/watch?v={{videoId}}' ><li>Open in HookTube</li></a>
|
||||
<li onclick='addSavedVideo("{{videoId}}")'>Save Video</li>
|
||||
<li onclick='playVideo("{{videoId}}", "", true); showVideoOptions(this.parentNode.previousSibling);'>Open (New Window)</li>
|
||||
<li onclick='addSavedVideo("{{videoId}}"); showVideoOptions(this.parentNode.previousSibling);'>Save Video</li>
|
||||
<a href='https://youtube.com/watch?v={{videoId}}; showVideoOptions(this.parentNode.previousSibling);' ><li>Open in YouTube</li></a>
|
||||
<a href='https://hooktube.com/watch?v={{videoId}}; showVideoOptions(this.parentNode.previousSibling);' ><li>Open in HookTube</li></a>
|
||||
{{{deleteHtml}}}
|
||||
</ul>
|
||||
</div>
|
||||
<div class='videoThumbnail'>
|
||||
<img onclick='playVideo("{{videoId}}")' src={{videoThumbnail}} />
|
||||
<img onclick='playVideo("{{videoId}}", "{{videoThumbnail}}")' src={{videoThumbnail}} />
|
||||
<p onclick='playVideo("{{videoId}}")' class='videoDuration'>{{videoDuration}}</p>
|
||||
</div>
|
||||
<p onclick='playVideo("{{videoId}}")' class='videoTitle'>{{videoTitle}}</p>
|
||||
|
|