When adding media from plex, the UI will now use dizqueTV as a proxy to connect to Plex. Thanks to this the 'ui route' is no longer necessary. Connection to plex through UI is still in use for authenticating with plex when adding the servers.

This commit is contained in:
vexorian 2025-12-08 10:23:47 -04:00
parent 4cdf87121a
commit 7daad9e33f
11 changed files with 132 additions and 73 deletions

View File

@ -32,6 +32,8 @@ const ProgrammingService = require("./src/services/programming-service");
const ActiveChannelService = require('./src/services/active-channel-service')
const ProgramPlayTimeDB = require('./src/dao/program-play-time-db')
const FfmpegSettingsService = require('./src/services/ffmpeg-settings-service')
const PlexProxyService = require('./src/services/plex-proxy-service')
const PlexServerDB = require('./src/dao/plex-server-db');
const onShutdown = require("node-graceful-shutdown").onShutdown;
@ -107,6 +109,9 @@ fillerDB = new FillerDB( path.join(process.env.DATABASE, 'filler') , channelServ
customShowDB = new CustomShowDB( path.join(process.env.DATABASE, 'custom-shows') );
let programPlayTimeDB = new ProgramPlayTimeDB( path.join(process.env.DATABASE, 'play-cache') );
let ffmpegSettingsService = new FfmpegSettingsService(db, unlockPath);
let plexServerDB = new PlexServerDB(channelService, fillerDB, customShowDB, db);
let plexProxyService = new PlexProxyService(plexServerDB);
async function initializeProgramPlayTimeDB() {
try {
@ -251,6 +256,35 @@ channelService.on("channel-update", (data) => {
let hdhr = HDHR(db, channelDB)
let app = express()
const responseInterceptor = (
req,
res,
next
) => {
let t0 = new Date().getTime();
const originalSend = res.send;
let responseSent = false;
console.log(`${req.method} ${req.url} ...`);
res.send = function (body) {
if (!responseSent) {
let t1 = new Date().getTime();
let dt = t1 - t0;
console.log(`${req.method} ${req.url} ${res.statusCode} in ${dt}ms`);
responseSent = true;
}
return originalSend.call(this, body);
};
next();
};
app.use(responseInterceptor);
eventService.setup(app);
app.use(
@ -290,7 +324,7 @@ app.use('/favicon.svg', express.static(
app.use('/custom.css', express.static(path.join(process.env.DATABASE, 'custom.css')))
// API Routers
app.use(api.router(db, channelService, fillerDB, customShowDB, xmltvInterval, guideService, m3uService, eventService, ffmpegSettingsService))
app.use(api.router(db, channelService, fillerDB, customShowDB, xmltvInterval, guideService, m3uService, eventService, ffmpegSettingsService, plexServerDB, plexProxyService))
app.use('/api/cache/images', cacheImageService.apiRouters())
app.use('/' + fontAwesome, express.static(path.join(process.env.DATABASE, fontAwesome)))
app.use('/' + bootstrap, express.static(path.join(process.env.DATABASE, bootstrap)))

View File

@ -24,10 +24,9 @@ function safeString(object) {
}
module.exports = { router: api }
function api(db, channelService, fillerDB, customShowDB, xmltvInterval, guideService, _m3uService, eventService, ffmpegSettingsService ) {
function api(db, channelService, fillerDB, customShowDB, xmltvInterval, guideService, _m3uService, eventService, ffmpegSettingsService, plexServerDB, plexProxyService ) {
let m3uService = _m3uService;
const router = express.Router()
const plexServerDB = new PlexServerDB(channelService, fillerDB, customShowDB, db);
router.get('/api/version', async (req, res) => {
try {
@ -216,7 +215,15 @@ function api(db, channelService, fillerDB, customShowDB, xmltvInterval, guideSe
);
}
})
router.get('/api/plex-server/:serverName64/:path(*)', async (req, res) => {
try {
let result = await plexProxyService.get(req.params.serverName64, req.params.path);
res.status(200).send(result);
} catch (err) {
console.error("Could not use plex proxy.", err);
res.status(404).send("Could not call plex server.");
}
});
// Channels
router.get('/api/channels', async (req, res) => {

View File

@ -15,6 +15,17 @@ class PlexServerDB
this.showDB = showDB;
}
async getPlexServerByName(name) {
let servers = this.db['plex-servers'].find()
let server = servers.sort( (a,b) => { return a.index - b.index } )
.filter( (server) => name === server.name )
[0];
if (typeof(server) === "undefined") {
return null;
}
return server;
}
async fixupAllChannels(name, newServer) {
let channelNumbers = await this.channelService.getAllChannelNumbers();
let report = await Promise.all( channelNumbers.map( async (i) => {

View File

@ -0,0 +1,30 @@
const Plex = require('../plex.js')
const events = require('events')
class PlexProxyService extends events.EventEmitter {
constructor(plexServerDB) {
super();
this.plexServerDB = plexServerDB;
}
async get(serverName64, path) {
let plexServer = await getPlexServer(this.plexServerDB, serverName64);
let client = new Plex(plexServer);
return { MediaContainer: await client.Get("/" + path) };
}
}
async function getPlexServer(plexServerDB, serverName64) {
let serverKey = Buffer.from(serverName64, 'base64').toString('utf-8');
let server = await plexServerDB.getPlexServerByName(serverKey);
if (server == null) {
throw Error("server not found");
}
return server;
}
module.exports = PlexProxyService

View File

@ -31,13 +31,7 @@ module.exports = function (plex, dizquetv, $timeout, commonProgramTools) {
});
}
scope.selectOrigin = function (origin) {
if ( origin.type === 'plex' ) {
scope.plexServer = origin.server;
updateLibrary(scope.plexServer);
} else {
scope.plexServer = undefined;
updateCustomShows();
}
updateLibrary(origin);
}
scope._onFinish = (s, insertPoint) => {
if (s.length > scope.limit) {
@ -99,20 +93,31 @@ module.exports = function (plex, dizquetv, $timeout, commonProgramTools) {
"type" : "plex",
"name" : `Plex - ${s.name}`,
"server": s,
"loaded" : false,
}
} );
scope.currentOrigin = scope.origins[0];
scope.plexServer = scope.currentOrigin.server;
scope.origins.push( {
"type": "dizquetv",
"name" : "dizqueTV - Custom Shows",
"loaded" : false,
} );
updateLibrary(scope.plexServer)
updateLibrary(scope.origins[0])
})
let updateLibrary = async(server) => {
let updateLibrary = async(origin) => {
scope.currentOrigin = origin;
origin.loaded = false;
if ( origin.type !== 'plex' ) {
scope.plexServer = undefined;
await updateCustomShows();
origin.loaded = true;
return;
}
let server = scope.currentOrigin.server;
let lib = await plex.getLibrary(server);
let play = await plex.getPlaylists(server);
scope.currentOrigin.loaded = true;
scope.plexServer = server;
play.forEach( p => {
p.type = "playlist";

View File

@ -20,12 +20,9 @@ module.exports = function (plex, dizquetv, $timeout) {
scope.servers = servers;
if(servers) {
for (let i = 0; i < scope.servers.length; i++) {
scope.servers[i].uiStatus = 0;
scope.servers[i].backendStatus = 0;
let t = (new Date()).getTime();
scope.servers[i].uiPending = t;
scope.servers[i].backendPending = t;
scope.refreshUIStatus(t, i);
scope.refreshBackendStatus(t, i);
}
}
@ -51,22 +48,6 @@ module.exports = function (plex, dizquetv, $timeout) {
scope.refreshServerList();
}
scope.isAnyUIBad = () => {
let t = (new Date()).getTime();
if(scope.servers) {
for (let i = 0; i < scope.servers.length; i++) {
let s = scope.servers[i];
if (
(s.uiStatus == -1)
|| ( (s.uiStatus == 0) && (s.uiPending + 30000 < t) )
) {
return true;
}
}
}
return false;
};
scope.isAnyBackendBad = () => {
let t = (new Date()).getTime();
if(scope.servers) {
@ -84,15 +65,6 @@ module.exports = function (plex, dizquetv, $timeout) {
};
scope.refreshUIStatus = async (t, i) => {
let s = await plex.check(scope.servers[i]);
if (scope.servers[i].uiPending == t) {
// avoid updating for a previous instance of the row
scope.servers[i].uiStatus = s;
}
scope.$apply();
};
scope.refreshBackendStatus = async (t, i) => {
let s = await dizquetv.checkExistingPlexServer(scope.servers[i].name);
if (scope.servers[i].backendPending == t) {

View File

@ -19,12 +19,10 @@
"minutes_to_sign_plex": "You have 2 minutes to sign into your Plex Account.",
"name": "Name",
"uri": "URI",
"ui_route": "UI Route",
"backend_route": "Backend Route",
"routeStatus": "Route Status",
"ok": "ok",
"error": "error",
"ui_bad": "If a Plex server configuration has problems with the UI route, the channel editor won't be able to access its content.",
"backend_bad": "If a Plex server configuration has problems with the backend route, dizqueTV won't be able to play its content.",
"backend_bad": "A route problem means the dizqueTV server can't establish a connection with the configured Plex server.",
"plex_transcoder_settings": "Plex Transcoder Settings",
"update": "Update",
"reset_options": "Reset Options",

View File

@ -43,7 +43,13 @@
</button>
Content:
</label>
<ul ng-show="currentOrigin.type=='plex' " class="list-group list-group-root plex-panel" ng-init="setHeight = {'height': height + 'px'}" ng-style="setHeight" lazy-img-container>
<ul ng-show="currentOrigin.loaded!==true" class="list-group list-group-root plex-panel" ng-init="setHeight = {'height': height + 'px'}" ng-style="setHeight" lazy-img-container>
<li class="list-group-item"><div class="loader"></div> <div>Loading Library Contents...</div></li>
</ul>
<ul ng-show="currentOrigin.type=='plex' && currentOrigin.loaded===true " class="list-group list-group-root plex-panel" ng-init="setHeight = {'height': height + 'px'}" ng-style="setHeight" lazy-img-container>
<li class="list-group-item" ng-repeat="a in libraries">
<div class="flex-container library-item-hover {{ displayImages ? 'w_images' : 'wo_images' }}" ng-click="getNested(a, true);">
<span class="fa {{ a.collapse ? 'fa-chevron-down' : 'fa-chevron-right' }} tab"></span>
@ -122,7 +128,7 @@
</ul>
</li>
</ul>
<ul ng-show="currentOrigin.type=='dizquetv' " class="list-group list-group-root plex-panel" ng-init="setHeight = {'height': height + 'px'}" ng-style="setHeight" lazy-img-container>
<ul ng-show="currentOrigin.type=='dizquetv' && currentOrigin.loaded===true" class="list-group list-group-root plex-panel" ng-init="setHeight = {'height': height + 'px'}" ng-style="setHeight" lazy-img-container>
<li class="list-group-item" ng-repeat="x in customShows">
<div class="flex-container" ng-click="addCustomShow(x);">
<span class="fa fa-plus-circle tab"></span>

View File

@ -16,8 +16,7 @@
<tr>
<th>{{'settings_server.name' | i18next}}</th>
<th>{{'settings_server.uri' | i18next}}</th>
<th>{{'settings_server.ui_route' | i18next}}</th>
<th>{{'settings_server.backend_route' | i18next}}</th>
<th>{{'settings_server.routeStatus' | i18next}}</th>
<th></th>
</tr>
<tr ng-if="servers.length === 0">
@ -31,11 +30,6 @@
<tr ng-repeat="x in servers" ng-hide="serversPending" >
<td>{{ x.name }}</td>
<td><span class='text-secondary text-small'>{{ x.uri }}</span></td>
<td>
<div class='loader' ng-if="x.uiStatus == 0"></div>
<div class='text-success' ng-if="x.uiStatus == 1"><i class='fa fa-check'></i>{{'settings_server.ok' | i18next}}</div>
<div class='text-danger' ng-if="x.uiStatus == -1"><i class='fa fa-exclamation-triangle'></i>{{'settings_server.error' | i18next}}</div>
</td>
<td>
<div class='loader' ng-if="x.backendStatus == 0"></div>
<div class='text-success' ng-if="x.backendStatus == 1"><i class='fa fa-check'></i>{{'settings_server.ok' | i18next}}</div>
@ -51,11 +45,6 @@
<p class="text-center text-danger small">{{serverError}}</p>
</td>
</tr>
<tr ng-if="isAnyUIBad()">
<td colspan="5">
<p class="text-center text-danger small">{{'settings_server.ui_bad' | i18next}}</p>
</td>
</tr>
<tr ng-if="isAnyBackendBad()">
<td colspan="5">
<p class="text-center text-danger small">{{'settings_server.server_bad' | i18next}}</p>

View File

@ -49,6 +49,15 @@ module.exports = function ($http, $q) {
});
return d.data;
},
getFromPlexProxy: async (serverName, path) => {
let serverName64 = Buffer.from(serverName, 'utf-8').toString('base64');
let tmp = await ($http({
method: 'GET',
url : `api/plex-server/${serverName64}${path}`,
headers: { "Cache-Control": "no-cache"},
}))
return tmp.data.MediaContainer;
},
getPlexSettings: () => {
return $http.get('/api/plex-settings').then((d) => { return d.data })
},

View File

@ -1,6 +1,6 @@
const Plex = require('../../src/plex');
module.exports = function ($http, $window, $interval) {
module.exports = function (dizquetv, $http, $window, $interval) {
let exported = {
login: async () => {
const headers = {
@ -108,14 +108,13 @@ module.exports = function ($http, $window, $interval) {
},
getLibrary: async (server) => {
var client = new Plex(server)
const res = await client.Get('/library/sections')
const res = await dizquetv.getFromPlexProxy(server.name, '/library/sections')
var sections = []
for (let i = 0, l = typeof res.Directory !== 'undefined' ? res.Directory.length : 0; i < l; i++)
if (res.Directory[i].type === 'movie' || res.Directory[i].type === 'show' || res.Directory[i].type === 'artist' ) {
var genres = []
if (res.Directory[i].type === 'movie') {
const genresRes = await client.Get(`/library/sections/${res.Directory[i].key}/genre`)
const genresRes = await dizquetv.getFromPlexProxy(server.name,`/library/sections/${res.Directory[i].key}/genre`)
for (let q = 0, k = typeof genresRes.Directory !== 'undefined' ? genresRes.Directory.length : 0; q < k; q++) {
if (genresRes.Directory[q].type === 'genre') {
genres.push({
@ -138,8 +137,7 @@ module.exports = function ($http, $window, $interval) {
return sections
},
getPlaylists: async (server) => {
var client = new Plex(server)
const res = await client.Get('/playlists')
const res = await dizquetv.getFromPlexProxy(server.name, '/playlists');
var playlists = []
for (let i = 0, l = typeof res.Metadata !== 'undefined' ? res.Metadata.length : 0; i < l; i++)
if (
@ -157,8 +155,7 @@ module.exports = function ($http, $window, $interval) {
return playlists
},
getStreams: async (server, key) => {
var client = new Plex(server)
return client.Get(key).then((res) => {
return dizquetv.getFromPlexProxy(server.name, key).then((res) => {
let streams = res.Metadata[0].Media[0].Part[0].Stream
for (let i = 0, l = streams.length; i < l; i++) {
if (typeof streams[i].key !== 'undefined') {
@ -169,9 +166,9 @@ module.exports = function ($http, $window, $interval) {
})
},
getNested: async (server, lib, includeCollections, errors) => {
var client = new Plex(server)
const key = lib.key
const res = await client.Get(key)
const res = await dizquetv.getFromPlexProxy(server.name, key)
const size = (typeof(res.Metadata) !== 'undefined') ? res.Metadata.length : 0;
var nested = []
@ -191,7 +188,8 @@ module.exports = function ($http, $window, $interval) {
albumKeys = Object.keys( albumKeys );
await Promise.all( albumKeys.map( async(albumKey) => {
try {
let album = await client.Get(albumKey);
let album = await dizquetv.getFromPlexProxy(
server.name, albumKey);
if ( (typeof(album)!=='undefined') && album.size == 1) {
album = album.Metadata[0];
}
@ -287,7 +285,7 @@ module.exports = function ($http, $window, $interval) {
let k = res.librarySectionID;
k = `/library/sections/${k}/collections`;
let collections = await client.Get(k);
let collections = await dizquetv.getFromPlexProxy(server.name, k);
if ( typeof(collections.Metadata) === 'undefined') {
collections.Metadata = [];
}