Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Commit

Permalink
youtube support
Browse files Browse the repository at this point in the history
  • Loading branch information
can3p committed Sep 7, 2017
1 parent b8df581 commit 48c8628
Show file tree
Hide file tree
Showing 10 changed files with 2,233 additions and 3 deletions.
6 changes: 6 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Changelog

## 0.4

* 2017-09-07 Minimal youtube player support
* 2017-09-05 Reload app menu item. Google music does not pickup new tracks without reload. Don't ask why!

## Earlier

* 2017-06-22 Last.fm support
* 2017-06-20 Listen for track changes in the webview. Now we can do external integrations!
* 2016-09-11 Hide main window on close button click
Expand Down
4 changes: 3 additions & 1 deletion lastfm.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ exports.init = function() {
exports.nowPlaying = function(track) {
if (!exports.isAuthorized()) return;

lfm.track.updateNowPlaying(track) // we don't care about response
lfm.track.updateNowPlaying(track, function(...args) {
console.log("last.fm updateNowPlaying response:", args);
}) // we don't care about response
}

exports.scrobble = function(track) {
Expand Down
61 changes: 59 additions & 2 deletions main.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
const {app, globalShortcut, BrowserWindow, ipcMain, Menu, MenuItem} = require('electron')
const {app, globalShortcut, BrowserWindow, ipcMain, Menu, MenuItem, session} = require('electron')
const lastfm = require('./lastfm');

const filter = {};
// we need filters because it's supper annoying to listen ads after every second song. Thanks
var ad_filters = [
/https:\/\/.*\.youtube\.com\/ad.*/,
/.*.doubleclick.net\/.*/,
/.*\/pagead\/lvz?.*/,
/.*-pagead-id\..*/,
/.*log_event.*/,
/.*log_interaction.*/,
/.*adServer.*/,
/.*cumulus-cloud.*/,
/.*youtube.com\/ptracking\?.*/,
/.*pagead.*/,
/.*adunit.*/,
/.*googlesyndication.*/,
/.*api\/ads.*/,
/.*api\/stats\/ads.*/,
/.*s\.youtube.com\/.*/,
/.*(googlevideo|youtube).com\/.*_204.*/,
];

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
let forceQuiteApp;
let playerName = 'gmusic';

function createWindow () {
// Create the browser window.
Expand All @@ -16,7 +38,7 @@ function createWindow () {
mainWindow.maximize();

// and load the index.html of the app.
mainWindow.loadURL('file://' + __dirname + '/index.html');
mainWindow.loadURL('file://' + __dirname + '/players/' + playerName + '/index.html');

mainWindow.on('close', function(e){
if (!forceQuiteApp) {
Expand Down Expand Up @@ -46,15 +68,30 @@ function createWindow () {
}) || console.log('MediaNextTrack binding failed');

ipcMain.on('player-song-change', function(e, arg) {
// console.log('player-song-change', arg);
lastfm.nowPlaying(arg);
});

ipcMain.on('player-scrobble-time', function(e, arg) {
// console.log('player-scrobble-time', arg);
lastfm.scrobble(arg);
});

lastfm.init();
updateMenu();
initFilters();
}

function checkFilters(url) {
return ad_filters.reduce(function(acc, filter) {
return acc || !!filter.exec(url);
}, false);
}

function initFilters() {
session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) => {
callback({cancel: checkFilters(details.url)});
})
}

function getMenuTemplate(lastFMEnabled) {
Expand All @@ -73,12 +110,32 @@ function getMenuTemplate(lastFMEnabled) {
{type: 'separator'},
{role: 'quit'}
]
},
{
label: "Switch Player",
submenu: [
playerName === 'gmusic' ?
{label: 'Youtube', click: function() {
switchPlayer('youtube', updateMenu);
}}
:
{label: 'Google Music', click: function() {
switchPlayer('gmusic', updateMenu);
}}
]
}
];

return template;
}

function switchPlayer(name, cb) {
playerName = name;
mainWindow.loadURL('file://' + __dirname + '/players/' + playerName + '/index.html');
cb();
}


function reloadPlayer(cb) {
mainWindow.reload();
cb();
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
16 changes: 16 additions & 0 deletions players/youtube/content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
var ipc = require('electron').ipcRenderer;

ipc.on('play-control', function(event, command){
var webView = document.querySelector('webview#gpm-player');
switch (command) {
case 'rewind':
webView.executeJavaScript("window.history.back()");
break;
case 'forward':
webView.executeJavaScript("document.querySelector('.ytp-next-button').click()");
break;
case 'play-pause':
webView.executeJavaScript("document.querySelector('.ytp-play-button').click()");
break;
}
});
16 changes: 16 additions & 0 deletions players/youtube/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<title>Perlotto</title>
<style>
* {
margin: 0;
padding: 0;
}
</style>
</head>
<body style="overflow: hidden">
<webview id="gpm-player" src="https://www.youtube.com" style="height:100%;width:100%;position:absolute;" preload="inject.js"></webview>
<script src="./content.js"></script>
</body>
</html>
91 changes: 91 additions & 0 deletions players/youtube/inject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
(function() {

const {ipcRenderer} = require('electron');
var currentTrack = {};
var updateTimer;

function scheduleScobbleUpdate(info) {
clearTimeout(updateTimer);

info.timestamp = Math.floor((+new Date()) / 1000);
updateTimer = setTimeout(function() {
ipcRenderer.send('player-scrobble-time', info)
}, 61000); // a bit bigger then minimal limit
}

function runWhenLoaded(func) {
if (playerLoadedp()) {
func();
return;
}

var timer = setInterval(function() {
if (playerLoadedp()) {
clearInterval(timer);
func();
return;
}
}, 200);
}

function runWhenTrackInfoChanges(func) {
var target = document.querySelector('#page');
target.focus();

var observer = new MutationObserver(() => func());

var config = {
childList: true,
subtree: true // see crbug.com/134322
};

observer.observe(target, config);
}

function isPlaying() {
var el = document.querySelector(".html5-main-video");
return el && !el.paused;
}

function playerLoadedp() {
return location.href.match(/\/watch\?v=/) && !!document.querySelector(".watch-title");
}

function trackInfo() {
var title = document.querySelector('.watch-title').title;
var parts = title.match(/((?:\w+\s+)+)\W+(.*)/);
if (parts && parts.length === 3) {
//best guess
return {
track: parts[2].trim(),
artist: parts[1].trim()
};
} else {
return {
track: title,
artist: "unknown"
};
}
}

function tracksEqualp(one, two) {
return one.track === two.track
&& one.album === two.album
&& one.artist === two.artist;
}

function analyzeTrackChange() {
var playing = isPlaying();
if (!playing) return;

var info = trackInfo();

if (!tracksEqualp(currentTrack, info)) {
currentTrack = info;
scheduleScobbleUpdate(info);
ipcRenderer.send('player-song-change', info)
}
}

runWhenLoaded(runWhenTrackInfoChanges.bind(null, analyzeTrackChange));
}());
Loading

0 comments on commit 48c8628

Please sign in to comment.