ElectronPlayer icon indicating copy to clipboard operation
ElectronPlayer copied to clipboard

Netflix no longer supported

Open zefir-git opened this issue 3 years ago • 3 comments

image Player version 2.0.7

zefir-git avatar Jun 02 '21 09:06 zefir-git

I have hopefully fixed this in the master branch by using the new Castlabs Electron EVS service. I am working on rebuilding the app from the ground up so I will release it when that is complete.

oscartbeaumont avatar Jun 10 '21 20:06 oscartbeaumont

// Modules to control application life and create native browser window

const fs = require('fs');
 const path = require('path');
  const { app, BrowserWindow, session, Menu, ipcMain, ipcRenderer } = require('electron'),
  Store = require('electron-store'),
  fetch = require('node-fetch');
  global.ipc = ipcRenderer

  app.commandLine.appendSwitch('widevine-cdm-path', path.join(__dirname, '/widevinecdmadapter.plugin'))
  app.commandLine.appendSwitch('widevine-cdm-version', '1.4.8.866')
  app.commandLine.appendSwitch('disable-features', 'IOSurfaceCapturer') 

const headerScript = fs.readFileSync(
  path.join(__dirname, 'client-header.js'),
  'utf8'
);

// Create Global Varibles
let mainWindow; // Global Windows Object
const menu = require('./menu');
const store = new Store();

// Analytics endpoint
const simpleAnalyticsEndpoint = "https://esa.otbeaumont.me/api";
let defaultUserAgent;

async function createWindow() {
  // Create the browser window.
  mainWindow = new BrowserWindow({
    width: 890,
    height: 600,
    webPreferences: {
      nodeIntegration: false,
      nodeIntegrationInWorker: false,
      contextIsolation: false, // Must be disabled for preload script. I am not aware of a workaround but this *shouldn't* effect security
      enableRemoteModule: false,
      plugins: true,
      preload: path.join(__dirname, 'client-preload.js'),
      setContentProtection: true
    },

    // Window Styling
    transparent: true,
    vibrancy: 'ultra-dark',
    frame: store.get('options.pictureInPicture')
      ? false
      : !store.get('options.hideWindowFrame'),
    alwaysOnTop: store.get('options.alwaysOnTop'),
    toolbar: true,
    backgroundColor: '#00000000',
    fullscreen: store.get('options.launchFullscreen')
  });

  mainWindow.setContentProtection(true);

  defaultUserAgent = mainWindow.webContents.userAgent;

  // Reset The Windows Size and Location
  let windowDetails = store.get('options.windowDetails');
  let relaunchWindowDetails = store.get('relaunch.windowDetails');
  if (relaunchWindowDetails) {
    mainWindow.setSize(
      relaunchWindowDetails.size[0],
      relaunchWindowDetails.size[1]
    );
    mainWindow.setPosition(
      relaunchWindowDetails.position[0],
      relaunchWindowDetails.position[1]
    );
    store.delete('relaunch.windowDetails');
  } else if (windowDetails) {
    mainWindow.setSize(windowDetails.size[0], windowDetails.size[1]);
    mainWindow.setPosition(
      windowDetails.position[0],
      windowDetails.position[1]
    );
  }

  // Configire Picture In Picture
  if (store.get('options.pictureInPicture') && process.platform === 'darwin') {
    app.dock.hide();
    mainWindow.setAlwaysOnTop(true, 'floating');
    mainWindow.setVisibleOnAllWorkspaces(true);
    mainWindow.setFullScreenable(false);
    app.dock.show();
  }

  // Detect and update version
  if (!store.get('version')) {
    store.set('version', app.getVersion());
    store.set('services', []);
    console.log('Initialised Config!');
  }

  // Load the services and merge the users and default services
  let userServices = store.get('services') || [];
  global.services = userServices;

  [
    {
      name: 'Netflix',
      logo: 'services/netflix.png',
      url: 'https://netflix.com/browse',
      color: '#e50914',
      style: {},
      permissions: []
    },
    {
      name: 'YouTube',
      logo: 'services/youtube.svg',
      url: 'https://youtube.com',
      color: '#ff0000',
      style: {},
      permissions: []
    },
    {
      name: 'YouTube TV',
      hidden: true,
      logo: 'services/youtube.svg',
      url: 'https://youtube.com/tv',
      color: '#ff0000',
      style: {},
      userAgent: "Mozilla/5.0 (SMART-TV; Linux; Tizen 4.0.0.2) AppleWebkit/605.1.15 (KHTML, like Gecko)",
      permissions: []
    },
    {
      name: 'Twitch',
      logo: 'services/twitch.svg',
      url: 'https://twitch.tv',
      color: '#6441a5',
      style: {},
      permissions: []
    },
    {
      name: 'Floatplane',
      hidden: true,
      logo: 'services/floatplane.svg',
      url: 'https://floatplane.com/',
      color: '#00aeef',
      style: {},
      permissions: []
    },
    {
      name: 'Hulu',
      hidden: true,
      logo: 'services/hulu.svg',
      url: 'https://www.hulu.com/',
      color: '#1ce783',
      style: {},
      permissions: []
    },
    {
      name: 'Amazon Prime Video',
      hidden: true,
      logo: 'services/amazon-prime-video.svg',
      url: 'https://www.primevideo.com/',
      color: '#46ABE2',
      style: {},
      permissions: []
    },
    {
      name: 'Disney+',
      hidden: true,
      logo: 'services/disney+.svg',
      url: 'https://www.disneyplus.com/',
      color: '#ffffff',
      style: {},
      permissions: []
    },
    {
      name: 'CBS All Access',
      hidden: true,
      logo: 'services/cbs-all-access.png',
      url: 'https://www.cbs.com/all-access/',
      color: '#4ca3dd',
      style: {},
      permissions: []
    },
    {
      name: 'Vudu',
      hidden: true,
      logo: 'services/vudu.svg',
      url: 'https://vudu.com',
      color: '#3399ff',
      style: {},
      permissions: []
    },
    {
      name: 'Crunchyroll',
      logo: 'services/crunchyroll.png',
      url: 'https://crunchyroll.com',
      color: '#ffe600',
      style: {},
      permissions: []
    }
  ].forEach(dservice => {
    let service = userServices.find(service => service.name == dservice.name);
    if (service) {
      global.services[userServices.indexOf(service)] = {
        name: service.name ? service.name : dservice.name,
        logo: service.logo ? service.logo : dservice.logo,
        url: service.url ? service.url : dservice.url,
        color: service.color ? service.color : dservice.color,
        style: service.style ? service.style : dservice.style,
        userAgent: service.userAgent ? service.userAgent : dservice.userAgent,
        permissions: service.permissions
          ? service.permissions
          : dservice.permissions,
        hidden: service.hidden != undefined ? service.hidden : dservice.hidden,
      };
    } else {
      dservice._defaultService = true;
      global.services.push(dservice);
    }
  });

  // Create The Menubar
  Menu.setApplicationMenu(menu(store, global.services, mainWindow, app, defaultUserAgent));

  // Load the UI or the Default Service
  let defaultService = store.get('options.defaultService'),
    lastOpenedPage = store.get('options.lastOpenedPage'),
    relaunchToPage = store.get('relaunch.toPage');

  if (relaunchToPage !== undefined) {
    console.log('Relaunching Page ' + relaunchToPage);
    mainWindow.loadURL(relaunchToPage);
    store.delete('relaunch.toPage');
  } else if (defaultService == 'lastOpenedPage' && lastOpenedPage) {
    console.log('Loading The Last Opened Page ' + lastOpenedPage);
    mainWindow.loadURL(lastOpenedPage);
  } else if (defaultService != undefined) {
    defaultService = global.services.find(
      service => service.name == defaultService
    );
    if (defaultService.url) {
      console.log('Loading The Default Service ' + defaultService.url);
      mainWindow.loadURL(defaultService.url);
      mainWindow.webContents.userAgent = defaultService.userAgent ? defaultService.userAgent : defaultUserAgent;
    } else {
      console.log(
        "Error Default Service Doesn't Have A URL Set. Falling back to the menu."
      );
      mainWindow.loadFile('src/ui/index.html');
    }
  } else {
    console.log('Loading The Main Menu');
    mainWindow.loadFile('src/ui/index.html');
  }

  // Emitted when the window is closing
  mainWindow.on('close', e => {
    // Save open service if lastOpenedPage is the default service
    if (store.get('options.defaultService') == 'lastOpenedPage') {
      store.set('options.lastOpenedPage', mainWindow.getURL());
    }

    // If enabled store the window details so they can be restored upon restart
    if (store.get('options.windowDetails')) {
      if (mainWindow) {
        store.set('options.windowDetails', {
          position: mainWindow.getPosition(),
          size: mainWindow.getSize()
        });
      } else {
        console.error(
          'Error window was not defined while trying to save windowDetails'
        );
        return;
      }
    }
  });

  // Inject Header Script On Page Load If In Frameless Window
  mainWindow.webContents.on('dom-ready', broswerWindowDomReady);

  // Emitted when the window is closed.
  mainWindow.on('closed', mainWindowClosed);

  // Emitted when website requests permissions - Electron default allows any permission this restricts websites
  mainWindow.webContents.session.setPermissionRequestHandler(
    (webContents, permission, callback) => {
      let websiteOrigin = new URL(webContents.getURL()).origin;
      let service = global.services.find(
        service => new URL(service.url).origin == websiteOrigin
      );

      if (
        (service &&
          service.permissions &&
          service.permissions.includes(permission)) ||
        permission == 'fullscreen'
      ) {
        console.log(
          `Allowed Requested Browser Permission '${permission}' For Site '${websiteOrigin}'`
        );
        return callback(true);
      }

      console.log(
        `Rejected Requested Browser Permission '${permission}' For Site '${websiteOrigin}'`
      );
      return callback(false);
    }
  );

  // Analytics
  // Simple Analytics is used which protects the users privacy. This tracking allow the developers to build
  // a better product with more insight into what devices it is being used on so better testing can be done.
  let unique = false;
  if(!store.get('_do_not_edit___date_')) {
    store.set('_do_not_edit___date_', (new Date()).getTime())
    unique = true;
  } else {
    let now = new Date();
    let lastPing = new Date(new Date(store.get('_do_not_edit___date_')));
    if (lastPing.getFullYear() !== now.getFullYear() || lastPing.getMonth() !== now.getMonth() || lastPing.getDate() !== now.getDate()) {
      store.set('_do_not_edit___date_', now.getTime())
      unique = true;
    }
  }

  // fetch(simpleAnalyticsEndpoint, {
  //     method: 'POST',
  //     headers: {
  //       "User-Agent": "ElectronPlayer",
  //       "Content-Type": "application/json",
  //       "Accept": "application/json"
  //     },
  //     body: JSON.stringify({
  //         url: "https://electronplayer.otbeaumont.me/" + store.get('version'),
  //         ua: mainWindow.webContents.userAgent,
  //         width: mainWindow.getSize()[0],
  //         unique: unique,
  //         timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
  //         urlReferrer: process.platform
  //     })
  // })
}

// This method is called when the broswer window's dom is ready
// it is used to inject the header if pictureInPicture mode and
// hideWindowFrame are enabled.
function broswerWindowDomReady() {
  if (
    store.get('options.pictureInPicture') ||
    store.get('options.hideWindowFrame')
  ) {
    // TODO: This is a temp fix and a propper fix should be developed
    if (mainWindow != null) {
      mainWindow.webContents.executeJavaScript(headerScript);
    }
  }
}

// Run when window is closed. This cleans up the mainWindow object to save resources.
function mainWindowClosed() {
  mainWindow = null;
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// The timeout fixes the trasparent background on Linux ???? why
app.on('ready', () => setTimeout(createWindow, 500));

// This is a custom event that is used to relaunch the application.
// It destroys and recreates the broswer window. This is used to apply
// settings that Electron doesn't allow to be changed on an active
// broswer window.
app.on('relaunch', () => {
  console.log('Relaunching The Application!');

  // Store details to remeber when relaunched
  if (mainWindow.getURL() != '') {
    store.set('relaunch.toPage', mainWindow.getURL());
  }
  store.set('relaunch.windowDetails', {
    position: mainWindow.getPosition(),
    size: mainWindow.getSize()
  });

  // Destory The BroswerWindow
  mainWindow.webContents.removeListener('dom-ready', broswerWindowDomReady);

  // Remove App Close Listener
  mainWindow.removeListener('closed', mainWindowClosed);

  // Close App
  mainWindow.close();
  mainWindow = undefined;

  // Create a New BroswerWindow
  createWindow();
});

// Chnage the windows url when told to by the ui
ipcMain.on('open-url', (e, service) => {
  console.log('Openning Service ' + service.name);
  mainWindow.webContents.userAgent = service.userAgent ? service.userAgent : defaultUserAgent;
  mainWindow.loadURL(service.url);
});

// Disable fullscreen when button pressed
ipcMain.on('exit-fullscreen', e => {
  if (store.get('options.pictureInPicture')) {
    store.delete('options.pictureInPicture');
  } else if (store.get('options.hideWindowFrame')) {
    store.delete('options.hideWindowFrame');
  }

  // Relaunch
  app.emit('relaunch');
});

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
app.on('activate', () => {
  if (mainWindow === null) {
    createWindow();
  }
});

 "devDependencies": {
    "electron": "https://github.com/castlabs/electron-releases#15.5.1-wvvmp",
    "electron-builder": "^22.3.2"
  },

and download https://github.com/tommoor/macflix/tree/master/WidevineCDM into /src

gregpalaci avatar Apr 20 '22 10:04 gregpalaci

https://github.com/gregpalaci/ElectronPlayer/releases/tag/v2.0.8

gregpalaci avatar Apr 20 '22 11:04 gregpalaci