From 35b828ed63847b07612f483bc5e3699589842be4 Mon Sep 17 00:00:00 2001 From: vexorian Date: Sat, 27 Aug 2022 18:33:19 -0400 Subject: [PATCH 01/23] Various changes, including nvidia test --- index.js | 4 ++++ src/constants.js | 2 +- src/ffmpeg.js | 4 ++-- src/offline-player.js | 5 +++++ src/program-player.js | 5 +++++ src/throttler.js | 13 +++++++------ src/video.js | 33 +++++++++++++++++++++++++++------ 7 files changed, 51 insertions(+), 15 deletions(-) diff --git a/index.js b/index.js index b97c8c7..da5ee00 100644 --- a/index.js +++ b/index.js @@ -309,6 +309,10 @@ function initDB(db, channelDB) { let data = fs.readFileSync(path.resolve(path.join(__dirname, 'resources/loading-screen.png'))) fs.writeFileSync(process.env.DATABASE + '/images/loading-screen.png', data) } + if (!fs.existsSync(process.env.DATABASE + '/images/black.png')) { + let data = fs.readFileSync(path.resolve(path.join(__dirname, 'resources/black.png'))) + fs.writeFileSync(process.env.DATABASE + '/images/black.png', data) + } if (!fs.existsSync( path.join(process.env.DATABASE, 'custom.css') )) { let data = fs.readFileSync(path.resolve(path.join(__dirname, 'resources', 'default-custom.css'))) fs.writeFileSync( path.join(process.env.DATABASE, 'custom.css'), data) diff --git a/src/constants.js b/src/constants.js index 49d5cf8..13be557 100644 --- a/src/constants.js +++ b/src/constants.js @@ -28,5 +28,5 @@ module.exports = { // staying active, it checks every 5 seconds PLAYED_MONITOR_CHECK_FREQUENCY: 5*1000, - VERSION_NAME: "1.5.0-development" + VERSION_NAME: "1.5.1-nvidiatest" } diff --git a/src/ffmpeg.js b/src/ffmpeg.js index 00ace6c..30a6895 100644 --- a/src/ffmpeg.js +++ b/src/ffmpeg.js @@ -471,7 +471,8 @@ class FFMPEG extends events.EventEmitter { `-c:v`, (transcodeVideo ? this.opts.videoEncoder : 'copy'), `-sc_threshold`, `1000000000`, ); - if (stillImage) { + // do not use -tune stillimage for nv + if (stillImage && ! this.opts.videoEncoder.toLowerCase().includes("nv") ) { ffmpegArgs.push('-tune', 'stillimage'); } } @@ -482,7 +483,6 @@ class FFMPEG extends events.EventEmitter { if ( transcodeVideo && (this.audioOnly !== true) ) { // add the video encoder flags ffmpegArgs.push( - `-b:v`, `${this.opts.videoBitrate}k`, `-maxrate:v`, `${this.opts.videoBitrate}k`, `-bufsize:v`, `${this.opts.videoBufSize}k` ); diff --git a/src/offline-player.js b/src/offline-player.js index 38d846e..9140041 100644 --- a/src/offline-player.js +++ b/src/offline-player.js @@ -18,6 +18,11 @@ class OfflinePlayer { context.channel.offlinePicture = `http://localhost:${process.env.PORT}/images/loading-screen.png`; context.channel.offlineSoundtrack = undefined; } + if (context.isInterlude === true) { + context.channel = JSON.parse( JSON.stringify(context.channel) ); + context.channel.offlinePicture = `http://localhost:${process.env.PORT}/images/black.png`; + context.channel.offlineSoundtrack = undefined; + } this.ffmpeg = new FFMPEG(context.ffmpegSettings, context.channel); this.ffmpeg.setAudioOnly(this.context.audioOnly); } diff --git a/src/program-player.js b/src/program-player.js index 260ff10..3d9fd1c 100644 --- a/src/program-player.js +++ b/src/program-player.js @@ -42,6 +42,11 @@ class ProgramPlayer { /* loading */ context.isLoading = true; this.delegate = new OfflinePlayer(false, context); + } else if (program.type === 'interlude') { + console.log("About to play interlude stream"); + /* interlude */ + context.isInterlude = true; + this.delegate = new OfflinePlayer(false, context); } else if (program.type === 'offline') { console.log("About to play offline stream"); /* offline */ diff --git a/src/throttler.js b/src/throttler.js index ac42a11..10e4c67 100644 --- a/src/throttler.js +++ b/src/throttler.js @@ -7,7 +7,9 @@ function equalItems(a, b) { if ( (typeof(a) === 'undefined') || a.isOffline || b.isOffline ) { return false; } - return ( a.type === b.type); + console.log("no idea how to compare this: " + JSON.stringify(a) ); + console.log(" with this: " + JSON.stringify(b) ); + return a.title === b.title; } @@ -17,15 +19,14 @@ function wereThereTooManyAttempts(sessionId, lineupItem) { let t1 = (new Date()).getTime(); let previous = cache[sessionId]; + let result = false; + if (typeof(previous) === 'undefined') { previous = cache[sessionId] = { t0: t1 - constants.TOO_FREQUENT * 5, lineupItem: null, }; - } - - let result = false; - if (t1 - previous.t0 < constants.TOO_FREQUENT) { + } else if (t1 - previous.t0 < constants.TOO_FREQUENT) { //certainly too frequent result = equalItems( previous.lineupItem, lineupItem ); } @@ -49,4 +50,4 @@ function wereThereTooManyAttempts(sessionId, lineupItem) { } -module.exports = wereThereTooManyAttempts; \ No newline at end of file +module.exports = wereThereTooManyAttempts; diff --git a/src/video.js b/src/video.js index fb6fefa..e4f6f96 100644 --- a/src/video.js +++ b/src/video.js @@ -167,6 +167,8 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS isFirst = true; } + let isBetween = ( (typeof req.query.between !== 'undefined') && (req.query.between=='1') ); + let ffmpegSettings = db['ffmpeg-settings'].find()[0] // Check if ffmpeg path is valid @@ -180,20 +182,35 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS // Get video lineup (array of video urls with calculated start times and durations.) - let lineupItem = channelCache.getCurrentLineupItem( channel.number, t0); + let prog = null; let brandChannel = channel; let redirectChannels = []; let upperBounds = []; + const GAP_DURATION = 750; if (isLoading) { lineupItem = { type: 'loading', - streamDuration: 40, - duration: 40, + title: "Loading Screen", + streamDuration: GAP_DURATION, + duration: GAP_DURATION, + redirectChannels: [channel], start: 0, }; - } else if (lineupItem != null) { + } else if (isBetween) { + lineupItem = { + type: 'interlude', + title: "Interlude Screen", + streamDuration: GAP_DURATION, + duration: GAP_DURATION, + redirectChannels: [channel], + start: 0, + }; + } else { + lineupItem = channelCache.getCurrentLineupItem( channel.number, t0); + } + if (lineupItem != null) { redirectChannels = lineupItem.redirectChannels; upperBounds = lineupItem.upperBounds; brandChannel = redirectChannels[ redirectChannels.length -1]; @@ -278,7 +295,7 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS lineupItem = lineup.shift(); } - if ( !isLoading && (lineupItem != null) ) { + if ( !isBetween && !isLoading && (lineupItem != null) ) { let upperBound = 1000000000; let beginningOffset = 0; if (typeof(lineupItem.beginningOffset) !== 'undefined') { @@ -317,7 +334,7 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS } console.log("========================================================="); - if (! isLoading) { + if (! isLoading && ! isBetween) { channelCache.recordPlayback(channel.number, t0, lineupItem); } if (wereThereTooManyAttempts(session, lineupItem)) { @@ -553,8 +570,12 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&first=0&session=${sessionId}&audioOnly=${audioOnly}'\n`; } data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&first=1&session=${sessionId}&audioOnly=${audioOnly}'\n` + + data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&between=1&session=${sessionId}&audioOnly=${audioOnly}'\n`; + for (var i = 0; i < maxStreamsToPlayInARow - 1; i++) { data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&session=${sessionId}&audioOnly=${audioOnly}'\n` + data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&between=1&session=${sessionId}&audioOnly=${audioOnly}'\n` } res.send(data) From 507decff772b8c330920ac15097625b9c4328f30 Mon Sep 17 00:00:00 2001 From: vexorian Date: Sat, 15 Oct 2022 15:47:58 -0400 Subject: [PATCH 02/23] black.png --- resources/black.png | Bin 0 -> 11801 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/black.png diff --git a/resources/black.png b/resources/black.png new file mode 100644 index 0000000000000000000000000000000000000000..72bdc8d64020bb9dcf04979a14efa685f8bf9763 GIT binary patch literal 11801 zcmeHN`8!m582?%_V@adb$Z`!48G|y3>Dq=c6jEf%nz7ZjO-0)kAtPI3OGvhi3Y9`7 zlp)eox3Uc>TOwUVE)DLCsej-;_lJJ?p7WgNywCfb^ZnsH?`JvhIq|mE=Bq`dMF0S+ zNfxGd03ad(5Y$BqaWbQ?8%+SJnaI1O^x*EiUP+~&f~Zq+gIu%wpUg>whMlDX zJwijz+?8JRc)dXKoc*_4ub#Qno^46r_Q*HQy^z|db~f$g<1rkaSP_);$Kq`Hnlt3C zZg3Cx0dMoa7@}$J#<_g zi^-3u`D8s=D94^Eu5_Sp&bo=;$?gd6)6giEzpH%tPJ^{VP_yI>d4z>Thl5}=E=85R zws`letVCo41(co7QJz}GU(_aN9J;nu_CjuFPk6H*>vrf!adWpIN^Rr!R^w8B2TSV0 zt)bGZWd$VrJJ}~*7Hqe&b_!!=bSfO!qkhR*N4whVErWL5cgJx}H&Jxs#!_W8*3FWh zZo(k0f28&qi$}YM7F$)E>K;B&8(=bD^`>f={M^O=ZdhjC{bRC{eJ4L1wUyFUHj^ey zSAHnGK-e3&kDN{TTJ`Bp`$EyN(lY_|Mv3Cv-xX!#>eG#7+JDpVlT*2$byiB{lym`( zU)9g8y2g%EiIjxZBcI+n<>ssky@+0a%T?BMfci-h>jUD5|eegz}9V zgnf^4cJHsf7vJ|Fco;8oOnxM;v%#&?{N_?Q``FCHDC<>qVSj2E^X1eF1@`JurN@_y zD_Lj&_>%lhOl(OeCO^9#0M*pfsrpt=Y$ap+OQ^fBB5LO;Pi^IzqEydEC)leKV{EaG zN407f*IvXcZP7@I3T!l{Oa;D_$J{5>h(zTh6*ZRl>Njke>Q#EFcoQxDvGsD8+ZR1F zifLcsge#0JVUg7&(3#p;FP01eV>@%zg`BY!MZNvMPvg2PzW`aOx^ zRNd^ilxcK_J~Q;)$PK#BNI%U+iuuVZAuVo;qJ31j>_5PgBPkrM?@Kr_&B1CaeE>;Egpj7}1Mqt(|3r7zmxccQD^MB1_fl_Mz~IZMVr{eZRL_hsOgCJix={+0yDjQ6W8A@ zv6ppE3;yL|G`*qA5X6cM)pZ>Dl2TtEp>DKL-Q53PGdGYztFsx#b7)D(%gWpoEOYn# zx@$B}MmX5QDGUJdChozPjun^TBoP$So?VDxQGU_&gfc(j-2h+!$<%m%c+cl%P@X`57n#D#xP^cP zS!oB@Av{z-$gI=>lEX?FAUQyC01yih3jpc@)CK5~R(b$pfma5o3s4sz79bV?)CH&u zaE`?5f&Y$$$!cH3Htr(P?3;g2rJFUJ1Kf`}oGlC0uyO`q9lsPW@*w^kW9~+J@KTY& zKWCg;3Xy!g+-AWa15mga0I+5Nz}A3p;!*=-4FDiHLP6m`0qQ0I&>ld0z?Fqx*&b9o zzb9~h84%(A3jqBsd?$b=hKpEeVxWoPv;j0R(8O@*0M0mmZ4>i<8W)Kj7Q#W_F_YK1 O51zEk+O(YL5%n+Mu4KXh literal 0 HcmV?d00001 From 5fa414af5a37c9a537026335b2f2ccc513e6f7eb Mon Sep 17 00:00:00 2001 From: vexorian Date: Fri, 10 Nov 2023 13:21:35 -0400 Subject: [PATCH 03/23] Github actions to build and push docker images. --- .github/workflows/development-tag.yaml | 13 +++++++++++ .github/workflows/docker-build.yaml | 31 ++++++++++++++++++++++++++ .github/workflows/edge-tag.yaml | 13 +++++++++++ .github/workflows/latest-tag.yaml | 13 +++++++++++ .github/workflows/named-tag.yaml | 13 +++++++++++ 5 files changed, 83 insertions(+) create mode 100644 .github/workflows/development-tag.yaml create mode 100644 .github/workflows/docker-build.yaml create mode 100644 .github/workflows/edge-tag.yaml create mode 100644 .github/workflows/latest-tag.yaml create mode 100644 .github/workflows/named-tag.yaml diff --git a/.github/workflows/development-tag.yaml b/.github/workflows/development-tag.yaml new file mode 100644 index 0000000..a3dc0ee --- /dev/null +++ b/.github/workflows/development-tag.yaml @@ -0,0 +1,13 @@ +name: Development Tag + +on: + push: + branches: + - dev/1.5.x + +jobs: + docker: + uses: ./.github/workflows/docker-build.yaml + with: + tag: development + secrets: inherit diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml new file mode 100644 index 0000000..ad808ec --- /dev/null +++ b/.github/workflows/docker-build.yaml @@ -0,0 +1,31 @@ +name: Docker Build + +on: + workflow_call: + inputs: + tag: + required: true + type: string + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + push: true + tags: vexorian/dizquetv:${{ inputs.tag }} diff --git a/.github/workflows/edge-tag.yaml b/.github/workflows/edge-tag.yaml new file mode 100644 index 0000000..d697a9f --- /dev/null +++ b/.github/workflows/edge-tag.yaml @@ -0,0 +1,13 @@ +name: Edge Tag + +on: + push: + branches: + - edge + +jobs: + docker: + uses: ./.github/workflows/docker-build.yaml + with: + tag: edge + secrets: inherit diff --git a/.github/workflows/latest-tag.yaml b/.github/workflows/latest-tag.yaml new file mode 100644 index 0000000..fa0b385 --- /dev/null +++ b/.github/workflows/latest-tag.yaml @@ -0,0 +1,13 @@ +name: Latest Tag + +on: + push: + branches: + - main + +jobs: + docker: + uses: ./.github/workflows/docker-build.yaml + with: + tag: latest + secrets: inherit diff --git a/.github/workflows/named-tag.yaml b/.github/workflows/named-tag.yaml new file mode 100644 index 0000000..6b6a22c --- /dev/null +++ b/.github/workflows/named-tag.yaml @@ -0,0 +1,13 @@ +name: Named Tag + +on: + push: + tags: + - '*' + +jobs: + docker: + uses: ./.github/workflows/docker-build.yaml + with: + tag: ${{ github.ref_name }} + secrets: inherit From 27375a21aec324df594da8e5b7db17578b1b0b55 Mon Sep 17 00:00:00 2001 From: vexorian Date: Fri, 10 Nov 2023 19:36:55 -0400 Subject: [PATCH 04/23] Automatically build and upload binary files for releases and development branch.. --- .github/workflows/binaries-build.yaml | 50 +++++++++++++++++++++ .github/workflows/development-binaries.yaml | 13 ++++++ .github/workflows/tag-binaries.yaml | 13 ++++++ 3 files changed, 76 insertions(+) create mode 100644 .github/workflows/binaries-build.yaml create mode 100644 .github/workflows/development-binaries.yaml create mode 100644 .github/workflows/tag-binaries.yaml diff --git a/.github/workflows/binaries-build.yaml b/.github/workflows/binaries-build.yaml new file mode 100644 index 0000000..f1d52e6 --- /dev/null +++ b/.github/workflows/binaries-build.yaml @@ -0,0 +1,50 @@ +name: Build Executables and Update Release + +on: + workflow_call: + inputs: + release: + required: true + type: string + +jobs: + release-files: + runs-on: ubuntu-latest + steps: + + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build dist image + uses: docker/build-push-action@v2 + with: + context: . + file: Dockerfile-builder + load: true + tags: builder + + - name: Run dist docker + run: | + docker run -v ./dist:/home/node/app/dist builder sh make_dist.sh + + + - name: Upload Files + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ inputs.release }} + files: | + ./dist/dizquetv-win-x64.exe + ./dist/dizquetv-win-x86.exe + ./dist/dizquetv-linux-x64 + ./dist/dizquetv-macos-x64 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/development-binaries.yaml b/.github/workflows/development-binaries.yaml new file mode 100644 index 0000000..1dc2ce5 --- /dev/null +++ b/.github/workflows/development-binaries.yaml @@ -0,0 +1,13 @@ +name: Development Binaries + +on: + push: + branches: + - dev/1.5.x + +jobs: + bianries: + uses: ./.github/workflows/binaries-build.yaml + with: + release: development-binaries + secrets: inherit diff --git a/.github/workflows/tag-binaries.yaml b/.github/workflows/tag-binaries.yaml new file mode 100644 index 0000000..695d251 --- /dev/null +++ b/.github/workflows/tag-binaries.yaml @@ -0,0 +1,13 @@ +name: Release Binaries + +on: + push: + tags: + - "*" + +jobs: + binaries: + uses: ./.github/workflows/binaries-build.yaml + with: + release: ${{ github.ref_name }} + secrets: inherit From 274f87dd7d17b56af3152472de397a1965581e9b Mon Sep 17 00:00:00 2001 From: vexorian Date: Fri, 10 Nov 2023 19:48:17 -0400 Subject: [PATCH 05/23] Bump version. --- README.md | 2 +- src/constants.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6ec4761..095f201 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# dizqueTV 1.5.0-development +# dizqueTV 1.5.1-development ![Discord](https://img.shields.io/discord/711313431457693727?logo=discord&logoColor=fff&style=flat-square) ![GitHub top language](https://img.shields.io/github/languages/top/vexorian/dizquetv?logo=github&style=flat-square) ![Docker Pulls](https://img.shields.io/docker/pulls/vexorian/dizquetv?logo=docker&logoColor=fff&style=flat-square) Create live TV channel streams from media on your Plex servers. diff --git a/src/constants.js b/src/constants.js index 49d5cf8..8dcbba2 100644 --- a/src/constants.js +++ b/src/constants.js @@ -28,5 +28,5 @@ module.exports = { // staying active, it checks every 5 seconds PLAYED_MONITOR_CHECK_FREQUENCY: 5*1000, - VERSION_NAME: "1.5.0-development" + VERSION_NAME: "1.5.1-development" } From de26a312f7c14caacd13d6938c8338d7bdd86074 Mon Sep 17 00:00:00 2001 From: vexorian Date: Fri, 10 Nov 2023 22:50:38 -0400 Subject: [PATCH 06/23] Also build nvidia image --- .github/workflows/docker-build.yaml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml index ad808ec..ab53489 100644 --- a/.github/workflows/docker-build.yaml +++ b/.github/workflows/docker-build.yaml @@ -10,6 +10,16 @@ on: jobs: build: runs-on: ubuntu-latest + strategy: + matrix: + include: + - name: Default + Dockerfile: Dockerfile + suffix: "" + - name: nvidia + Dockerfile: Dockerfile-nvidia + suffix: "-nvidia" + steps: - name: Checkout Repository uses: actions/checkout@v3 @@ -28,4 +38,12 @@ jobs: with: context: . push: true - tags: vexorian/dizquetv:${{ inputs.tag }} + tags: vexorian/dizquetv:${{ inputs.tag }}${{ matrix.suffix }} + + - name: Build and push nvidia + uses: docker/build-push-action@v2 + with: + context: . + file: ${{ matrix.Dockerfile }} + push: true + tags: vexorian/dizquetv:${{ inputs.tag }}${{ matrix.suffix }} From a401703304f7b00ef2de1f92faef4dc769e11273 Mon Sep 17 00:00:00 2001 From: vexorian Date: Fri, 10 Nov 2023 23:13:12 -0400 Subject: [PATCH 07/23] Use github actions cache for Docker builds. --- .github/workflows/binaries-build.yaml | 2 ++ .github/workflows/docker-build.yaml | 10 ++-------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/binaries-build.yaml b/.github/workflows/binaries-build.yaml index f1d52e6..5ee59ad 100644 --- a/.github/workflows/binaries-build.yaml +++ b/.github/workflows/binaries-build.yaml @@ -31,6 +31,8 @@ jobs: file: Dockerfile-builder load: true tags: builder + cache-from: type=gha + cache-to: type=gha,mode=max - name: Run dist docker run: | diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml index ab53489..ef1cd73 100644 --- a/.github/workflows/docker-build.yaml +++ b/.github/workflows/docker-build.yaml @@ -39,11 +39,5 @@ jobs: context: . push: true tags: vexorian/dizquetv:${{ inputs.tag }}${{ matrix.suffix }} - - - name: Build and push nvidia - uses: docker/build-push-action@v2 - with: - context: . - file: ${{ matrix.Dockerfile }} - push: true - tags: vexorian/dizquetv:${{ inputs.tag }}${{ matrix.suffix }} + cache-from: type=gha + cache-to: type=gha,mode=max From 18491bf70fbf5d87f53caac3b596368e09492c54 Mon Sep 17 00:00:00 2001 From: vexorian Date: Sat, 11 Nov 2023 12:00:53 -0400 Subject: [PATCH 08/23] Docker login not needed for binaries build. Fix Typo. --- .github/workflows/binaries-build.yaml | 6 ------ .github/workflows/development-binaries.yaml | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/binaries-build.yaml b/.github/workflows/binaries-build.yaml index 5ee59ad..f5e52b4 100644 --- a/.github/workflows/binaries-build.yaml +++ b/.github/workflows/binaries-build.yaml @@ -18,12 +18,6 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - - name: Login to DockerHub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build dist image uses: docker/build-push-action@v2 with: diff --git a/.github/workflows/development-binaries.yaml b/.github/workflows/development-binaries.yaml index 1dc2ce5..4a2705b 100644 --- a/.github/workflows/development-binaries.yaml +++ b/.github/workflows/development-binaries.yaml @@ -6,7 +6,7 @@ on: - dev/1.5.x jobs: - bianries: + binaries: uses: ./.github/workflows/binaries-build.yaml with: release: development-binaries From 2e3c0b63b29b2a3e3928e8b8d098084458d3a17f Mon Sep 17 00:00:00 2001 From: vexorian Date: Sat, 11 Nov 2023 12:03:01 -0400 Subject: [PATCH 09/23] I *think* this change fixes somethign with music libraries. Let's see --- web/directives/plex-library.js | 2 +- web/services/plex.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/directives/plex-library.js b/web/directives/plex-library.js index 23f5e25..23b89d9 100644 --- a/web/directives/plex-library.js +++ b/web/directives/plex-library.js @@ -69,7 +69,7 @@ module.exports = function (plex, dizquetv, $timeout, commonProgramTools) { } } scope.selectLibrary = async (library) => { - await scope.fillNestedIfNecessary(library); + await scope.fillNestedIfNecessary(library, true); let p = library.nested.length; scope.pending += library.nested.length; try { diff --git a/web/services/plex.js b/web/services/plex.js index 35575ff..18913bc 100644 --- a/web/services/plex.js +++ b/web/services/plex.js @@ -283,7 +283,7 @@ module.exports = function ($http, $window, $interval) { console.error(msg , err); } } - if ( (includeCollections === true) && (res.viewGroup !== "artist" ) ) { + if (includeCollections === true) { let k = res.librarySectionID; k = `/library/sections/${k}/collections`; From ad1302aae418311701a2e6f153d8b145810c13be Mon Sep 17 00:00:00 2001 From: vexorian Date: Sun, 12 Nov 2023 11:00:22 -0400 Subject: [PATCH 10/23] Use the median to decide what filler to play. This has memory consequences unfortunately. --- Dockerfile | 2 +- package.json | 3 ++- src/helperFuncs.js | 34 +++++++++++++++++++++++++++++++--- src/video.js | 13 +++++++++++-- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 23435cc..b2abb0a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM node:12.18-alpine3.12 WORKDIR /home/node/app -COPY package*.json ./ +COPY package.json ./ RUN npm install && npm install -g browserify nexe@3.3.7 COPY --from=vexorian/dizquetv:nexecache /var/nexe/linux-x64-12.16.2 /var/nexe/ COPY . . diff --git a/package.json b/package.json index 2bb8697..a944c5d 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,8 @@ "node-ssdp": "^4.0.0", "random-js": "2.1.0", "request": "^2.88.2", - "uuid": "^8.0.0", + "quickselect": "2.0.0", + "uuid" : "9.0.1", "xml-writer": "^1.7.0" }, "bin": "dist/index.js", diff --git a/src/helperFuncs.js b/src/helperFuncs.js index 98e7552..9ed0755 100644 --- a/src/helperFuncs.js +++ b/src/helperFuncs.js @@ -8,6 +8,7 @@ module.exports = { let channelCache = require('./channel-cache'); const SLACK = require('./constants').SLACK; const randomJS = require("random-js"); +const quickselect = require("quickselect"); const Random = randomJS.Random; const random = new Random( randomJS.MersenneTwister19937.autoSeed() ); @@ -194,7 +195,11 @@ function pickRandomWithMaxDuration(channel, fillers, maxDuration) { } let listM = 0; let fillerId = undefined; - for (let j = 0; j < fillers.length; j++) { + + let median = getMedian(channelCache, channel, fillers); + + for (let medianCheck = 1; medianCheck >= 0; medianCheck--) { + for (let j = 0; j < fillers.length; j++) { list = fillers[j].content; let pickedList = false; let n = 0; @@ -204,6 +209,9 @@ function pickRandomWithMaxDuration(channel, fillers, maxDuration) { // a few extra milliseconds won't hurt anyone, would it? dun dun dun if (clip.duration <= maxDuration + SLACK ) { let t1 = channelCache.getProgramLastPlayTime( channel.number, clip ); + if ( (medianCheck==1) && (t1 > median) ) { + continue; + } let timeSince = ( (t1 == 0) ? D : (t0 - t1) ); if (timeSince < channel.fillerRepeatCooldown - SLACK) { @@ -247,11 +255,13 @@ function pickRandomWithMaxDuration(channel, fillers, maxDuration) { } } } + } + if (pick1 != null) { + break; + } } let pick = pick1; - let pickTitle = "null"; if (pick != null) { - pickTitle = pick.title; pick = JSON.parse( JSON.stringify(pick) ); pick.fillerId = fillerId; } @@ -322,6 +332,24 @@ function getWatermark( ffmpegSettings, channel, type) { } +function getMedian(channelCache, channel, fillers) { + let times = []; + for (let j = 0; j < fillers.length; j++) { + list = fillers[j].content; + for (let i = 0; i < list.length; i++) { + let clip = list[i]; + let t = channelCache.getProgramLastPlayTime( channel.number, clip); + times.push(t); + } + } + if (times.length == 0) { + return null; + } + quickselect(times, times.length / 2) + return times[times.length / 2]; + +} + function generateChannelContext(channel) { let channelContext = {}; for (let i = 0; i < CHANNEL_CONTEXT_KEYS.length; i++) { diff --git a/src/video.js b/src/video.js index e4f6f96..9f1ff4d 100644 --- a/src/video.js +++ b/src/video.js @@ -291,8 +291,17 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS throw "No video to play, this means there's a serious unexpected bug or the channel db is corrupted." } let fillers = await fillerDB.getFillersFromChannel(brandChannel); - let lineup = helperFuncs.createLineup(prog, brandChannel, fillers, isFirst) - lineupItem = lineup.shift(); + try { + let lineup = helperFuncs.createLineup(prog, brandChannel, fillers, isFirst) + lineupItem = lineup.shift(); + } catch (err) { + console.log("Error when attempting to pick video: " +err.stack); + lineupItem = { + isOffline: true, + err: err, + duration : 60000, + }; + } } if ( !isBetween && !isLoading && (lineupItem != null) ) { From 4b80c6f0e5b4703cb685c760989118a275d28e48 Mon Sep 17 00:00:00 2001 From: vexorian Date: Sun, 12 Nov 2023 11:01:33 -0400 Subject: [PATCH 11/23] Remove git hooks. Sorry, it's annoying. --- .husky/commit-msg | 4 ---- .husky/prepare-commit-msg | 4 ---- package.json | 14 +++----------- 3 files changed, 3 insertions(+), 19 deletions(-) delete mode 100755 .husky/commit-msg delete mode 100755 .husky/prepare-commit-msg diff --git a/.husky/commit-msg b/.husky/commit-msg deleted file mode 100755 index fe4c17a..0000000 --- a/.husky/commit-msg +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -npx --no-install commitlint --edit "" diff --git a/.husky/prepare-commit-msg b/.husky/prepare-commit-msg deleted file mode 100755 index 5c18e4b..0000000 --- a/.husky/prepare-commit-msg +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -exec < /dev/tty && node_modules/.bin/cz --hook || true diff --git a/package.json b/package.json index a944c5d..7182ff1 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,7 @@ "dev-server": "nodemon index.js --ignore ./web/ --ignore ./db/ --ignore ./xmltv.xml", "compile": "babel index.js -d dist && babel src -d dist/src", "package": "sh ./make_dist.sh", - "clean": "del-cli --force ./bin ./dist ./.dizquetv ./web/public/bundle.js", - "prepare": "husky install" + "clean": "del-cli --force ./bin ./dist ./.dizquetv ./web/public/bundle.js" }, "author": "vexorian", "license": "Zlib", @@ -35,10 +34,10 @@ "ng-i18next": "^1.0.7", "node-graceful-shutdown": "1.1.0", "node-ssdp": "^4.0.0", + "quickselect": "2.0.0", "random-js": "2.1.0", "request": "^2.88.2", - "quickselect": "2.0.0", - "uuid" : "9.0.1", + "uuid": "9.0.1", "xml-writer": "^1.7.0" }, "bin": "dist/index.js", @@ -51,9 +50,7 @@ "@commitlint/config-conventional": "^12.1.4", "browserify": "^16.5.1", "copyfiles": "^2.2.0", - "cz-conventional-changelog": "^3.3.0", "del-cli": "^3.0.0", - "husky": "^7.0.0", "nexe": "^3.3.7", "nodemon": "^2.0.3", "watchify": "^3.11.1" @@ -62,10 +59,5 @@ "plugins": [ "@babel/plugin-proposal-class-properties" ] - }, - "config": { - "commitizen": { - "path": "./node_modules/cz-conventional-changelog" - } } } From c8a9c9ea53a84a383c42bb9202d6837b214bfaff Mon Sep 17 00:00:00 2001 From: vexorian Date: Sun, 12 Nov 2023 12:47:22 -0400 Subject: [PATCH 12/23] Change GAP. --- src/constants.js | 5 +++++ src/ffmpeg.js | 9 +++++++-- src/video.js | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/constants.js b/src/constants.js index 8dcbba2..44abf88 100644 --- a/src/constants.js +++ b/src/constants.js @@ -5,6 +5,11 @@ module.exports = { TVGUIDE_MAXIMUM_FLEX_DURATION : 6 * 60 * 60 * 1000, TOO_FREQUENT: 1000, + // Duration of things like the loading screen and the interlude (the black + // frame that appears between videos). The theory is that we don't need + // it to last longer than one frame, but I am not so sure. + GAP_DURATION: 83, + //when a channel is forcibly stopped due to an update, let's mark it as active // for a while during the transaction just in case. CHANNEL_STOP_SHIELD : 5000, diff --git a/src/ffmpeg.js b/src/ffmpeg.js index 30a6895..42a0800 100644 --- a/src/ffmpeg.js +++ b/src/ffmpeg.js @@ -198,8 +198,9 @@ class FFMPEG extends events.EventEmitter { iW = this.wantedW; iH = this.wantedH; + let durstr = `duration=${streamStats.duration}ms`; + if (this.audioOnly !== true) { - ffmpegArgs.push("-r" , "24"); let pic = null; //does an image to play exist? @@ -216,6 +217,7 @@ class FFMPEG extends events.EventEmitter { } if (pic != null) { + ffmpegArgs.push("-r" , "24"); ffmpegArgs.push( '-i', pic, ); @@ -235,6 +237,8 @@ class FFMPEG extends events.EventEmitter { //this tune apparently makes the video compress better // when it is the same image stillImage = true; + this.volumePercent = Math.min(70, this.volumePercent); + } else if (this.opts.errorScreen == 'static') { ffmpegArgs.push( '-f', 'lavfi', @@ -269,7 +273,7 @@ class FFMPEG extends events.EventEmitter { videoComplex = `;realtime[videox]`; } } - let durstr = `duration=${streamStats.duration}ms`; + if (typeof(streamUrl.errorTitle) !== 'undefined') { //silent audioComplex = `;aevalsrc=0:${durstr}[audioy]`; @@ -549,6 +553,7 @@ class FFMPEG extends events.EventEmitter { if (this.hasBeenKilled) { return ; } + //console.log(this.ffmpegPath + " " + ffmpegArgs.join(" ") ); this.ffmpeg = spawn(this.ffmpegPath, ffmpegArgs, { stdio: ['ignore', 'pipe', (doLogs?process.stderr:"ignore") ] } ); if (this.hasBeenKilled) { console.log("Send SIGKILL to ffmpeg"); diff --git a/src/video.js b/src/video.js index 9f1ff4d..152ad94 100644 --- a/src/video.js +++ b/src/video.js @@ -188,7 +188,7 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS let redirectChannels = []; let upperBounds = []; - const GAP_DURATION = 750; + const GAP_DURATION = constants.GAP_DURATION; if (isLoading) { lineupItem = { type: 'loading', From 92cd5ecf8efc05111673e74d77e4a38921b543ab Mon Sep 17 00:00:00 2001 From: vexorian Date: Tue, 14 Nov 2023 09:38:50 -0400 Subject: [PATCH 13/23] Do not stop the queue after 100 videos played. The workaround is hopefully temporary because the transition between the batches of 100 is sort of glitchy --- src/video.js | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src/video.js b/src/video.js index 152ad94..605b715 100644 --- a/src/video.js +++ b/src/video.js @@ -51,7 +51,10 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS }) }) // Continuously stream video to client. Leverage ffmpeg concat for piecing together videos - let concat = async (req, res, audioOnly) => { + let concat = async (req, res, audioOnly, step) => { + if ( typeof(step) === 'undefined') { + step = 0; + } if (stopPlayback) { res.status(503).send("Server is shutting down.") return; @@ -78,9 +81,11 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS return } - res.writeHead(200, { - 'Content-Type': 'video/mp2t' - }) + if (step == 0) { + res.writeHead(200, { + 'Content-Type': 'video/mp2t' + }) + } console.log(`\r\nStream starting. Channel: ${channel.number} (${channel.name})`) @@ -107,7 +112,7 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS return; }) - ffmpeg.on('close', stop) + //ffmpeg.on('close', stop) res.on('close', () => { // on HTTP close, kill ffmpeg console.log(`\r\nStream ended. Channel: ${channel.number} (${channel.name})`); @@ -115,13 +120,13 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS }) ffmpeg.on('end', () => { - console.log("Video queue exhausted. Either you played 100 different clips in a row or there were technical issues that made all of the possible 100 attempts fail.") - stop(); + console.log("Queue exhausted so we are appending the channel stream again to the http output.") + concat(req, res, audioOnly, step+1); }) let channelNum = parseInt(req.query.channel, 10) - let ff = await ffmpeg.spawnConcat(`http://localhost:${process.env.PORT}/playlist?channel=${channelNum}&audioOnly=${audioOnly}`); - ff.pipe(res ); + let ff = await ffmpeg.spawnConcat(`http://localhost:${process.env.PORT}/playlist?channel=${channelNum}&audioOnly=${audioOnly}&stepNumber={step}`); + ff.pipe(res, { end: false} ); }; router.get('/video', async(req, res) => { return await concat(req, res, false); @@ -328,6 +333,9 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS } } + let t2 = (new Date()).getTime(); + console.log( `Decision Latency: (${t2-t0})ms` ); + console.log("========================================================="); console.log("! Start playback"); @@ -389,7 +397,7 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS try { playerObj = await player.play(res); t1 = (new Date()).getTime(); - console.log("Latency: (" + (t1- t0) ); + console.log( `Player Latency: (${t1-t0})ms` ); } catch (err) { console.log("Error when attempting to play video: " +err.stack); try { @@ -549,6 +557,10 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS return } + let stepNumber = parseInt(req.query.stepNumber, 10) + if (isNaN(stepNumber)) { + stepNumber = 0; + } let channelNum = parseInt(req.query.channel, 10) let channel = await channelService.getChannel(channelNum ); if (channel == null) { @@ -574,15 +586,20 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS && (ffmpegSettings.normalizeResolution === true) && (ffmpegSettings.normalizeAudio === true) && (audioOnly !== true) /* loading screen is pointless in audio mode (also for some reason it makes it fail when codec is aac, and I can't figure out why) */ + && (stepNumber == 0) ) { //loading screen data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&first=0&session=${sessionId}&audioOnly=${audioOnly}'\n`; } - data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&first=1&session=${sessionId}&audioOnly=${audioOnly}'\n` + let remaining = maxStreamsToPlayInARow; + if (stepNumber == 0) { + data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&first=1&session=${sessionId}&audioOnly=${audioOnly}'\n` - data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&between=1&session=${sessionId}&audioOnly=${audioOnly}'\n`; + data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&between=1&session=${sessionId}&audioOnly=${audioOnly}'\n`; + remaining--; + } - for (var i = 0; i < maxStreamsToPlayInARow - 1; i++) { + for (var i = 0; i < remaining; i++) { data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&session=${sessionId}&audioOnly=${audioOnly}'\n` data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&between=1&session=${sessionId}&audioOnly=${audioOnly}'\n` } From 66804fe26c7ee1797f2bf38790b424c8d46ede16 Mon Sep 17 00:00:00 2001 From: vexorian Date: Tue, 14 Nov 2023 09:39:27 -0400 Subject: [PATCH 14/23] Improve interlude --- src/constants.js | 8 +++++--- src/ffmpeg.js | 14 +++++++++++--- src/program-player.js | 1 + src/video.js | 2 ++ 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/constants.js b/src/constants.js index 44abf88..6a9619c 100644 --- a/src/constants.js +++ b/src/constants.js @@ -6,9 +6,11 @@ module.exports = { TOO_FREQUENT: 1000, // Duration of things like the loading screen and the interlude (the black - // frame that appears between videos). The theory is that we don't need - // it to last longer than one frame, but I am not so sure. - GAP_DURATION: 83, + // frame that appears between videos). The goal of these things is to + // prevent the video from getting stuck on the last second, which looks bad + // for some reason ~750 works well. I raised the fps to 60 and now 420 works + // but I wish it was lower. + GAP_DURATION: 10*42, //when a channel is forcibly stopped due to an update, let's mark it as active // for a while during the transaction just in case. diff --git a/src/ffmpeg.js b/src/ffmpeg.js index 42a0800..779e379 100644 --- a/src/ffmpeg.js +++ b/src/ffmpeg.js @@ -217,7 +217,11 @@ class FFMPEG extends events.EventEmitter { } if (pic != null) { - ffmpegArgs.push("-r" , "24"); + if (this.opts.noRealTime === true) { + ffmpegArgs.push("-r" , "60"); + } else { + ffmpegArgs.push("-r" , "24"); + } ffmpegArgs.push( '-i', pic, ); @@ -232,8 +236,12 @@ class FFMPEG extends events.EventEmitter { videoComplex = `;[${inputFiles++}:0]format=yuv420p[formatted]`; videoComplex +=`;[formatted]scale=w=${iW}:h=${iH}:force_original_aspect_ratio=1[scaled]`; videoComplex += `;[scaled]pad=${iW}:${iH}:(ow-iw)/2:(oh-ih)/2[padded]`; - videoComplex += `;[padded]loop=loop=-1:size=1:start=0[looped]`; - videoComplex +=`;[looped]realtime[videox]`; + videoComplex += `;[padded]loop=loop=-1:size=1:start=0`; + if (this.opts.noRealTime !== true) { + videoComplex +=`[looped];[looped]realtime[videox]`; + } else { + videoComplex +=`[videox]` + } //this tune apparently makes the video compress better // when it is the same image stillImage = true; diff --git a/src/program-player.js b/src/program-player.js index 3d9fd1c..d7ceea4 100644 --- a/src/program-player.js +++ b/src/program-player.js @@ -34,6 +34,7 @@ class ProgramPlayer { // people might want the codec normalization to stay because of player support context.ffmpegSettings.normalizeResolution = false; } + context.ffmpegSettings.noRealTime = program.noRealTime; if ( typeof(program.err) !== 'undefined') { console.log("About to play error stream"); this.delegate = new OfflinePlayer(true, context); diff --git a/src/video.js b/src/video.js index 605b715..11b02bc 100644 --- a/src/video.js +++ b/src/video.js @@ -198,6 +198,7 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS lineupItem = { type: 'loading', title: "Loading Screen", + noRealTime: true, streamDuration: GAP_DURATION, duration: GAP_DURATION, redirectChannels: [channel], @@ -207,6 +208,7 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS lineupItem = { type: 'interlude', title: "Interlude Screen", + noRealTime: true, streamDuration: GAP_DURATION, duration: GAP_DURATION, redirectChannels: [channel], From 17094ea64d802eb2a26841431c90fdf2e698dc52 Mon Sep 17 00:00:00 2001 From: vexorian Date: Wed, 15 Nov 2023 01:02:37 -0400 Subject: [PATCH 15/23] Sort fillter and custom shows lists. --- web/controllers/custom-shows.js | 4 ++++ web/controllers/filler.js | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/web/controllers/custom-shows.js b/web/controllers/custom-shows.js index d3b9378..a593d16 100644 --- a/web/controllers/custom-shows.js +++ b/web/controllers/custom-shows.js @@ -8,6 +8,10 @@ module.exports = function ($scope, $timeout, dizquetv) { $scope.shows = [ { id: '?', pending: true} ] $timeout(); let shows = await dizquetv.getAllShowsInfo(); + shows.sort( (a,b) => { + return a.name > b.name; + } ); + $scope.shows = shows; $timeout(); } diff --git a/web/controllers/filler.js b/web/controllers/filler.js index 0fd821b..773f47e 100644 --- a/web/controllers/filler.js +++ b/web/controllers/filler.js @@ -8,13 +8,14 @@ module.exports = function ($scope, $timeout, dizquetv) { $scope.fillers = [ { id: '?', pending: true} ] $timeout(); let fillers = await dizquetv.getAllFillersInfo(); + fillers.sort( (a,b) => { + return a.name > b.name; + } ); $scope.fillers = fillers; $timeout(); } $scope.refreshFiller(); - - let feedToFillerConfig = () => {}; let feedToDeleteFiller = feedToFillerConfig; From de3a64c4c08f44298156f002c5025bbe0cd706e2 Mon Sep 17 00:00:00 2001 From: vexorian Date: Wed, 15 Nov 2023 01:04:05 -0400 Subject: [PATCH 16/23] Tweak the library browser, the placement of the add button is now consistent. The background of the row gets highlighted blue so that you know that the plus button is for a specific library element. --- web/public/style.css | 4 ++ web/public/templates/plex-library.html | 56 +++++++++++++++++--------- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/web/public/style.css b/web/public/style.css index 0ad24b1..4f63c88 100644 --- a/web/public/style.css +++ b/web/public/style.css @@ -390,4 +390,8 @@ div.programming-programs div.list-group-item { width: 1em; height: 1em; margin-bottom: 0.25em; +} + +.list-group-item .library-item-hover:hover { + background: #D0D0FF } \ No newline at end of file diff --git a/web/public/templates/plex-library.html b/web/public/templates/plex-library.html index 5fd6218..2410b65 100644 --- a/web/public/templates/plex-library.html +++ b/web/public/templates/plex-library.html @@ -28,46 +28,55 @@ +
+ +
  • -
    +
    {{ displayTitle(a) }} - +
    • -
      - - {{ displayTitle(b) }} - + {{ displayTitle(b) }} + {{b.durationStr}} + + + - + - +
      • -
        - + - {{ displayTitle(c) }} + {{ displayTitle(c) }} + class=""> {{c.durationStr}} + + + - +
        • -
          - +
          + - {{ displayTitle(d) }} - {{d.durationStr}} + {{ displayTitle(d) }} + {{d.durationStr}} + + +
        • From 447c33027bc85d9aefff9e36f79e27741b6fb42d Mon Sep 17 00:00:00 2001 From: vexorian Date: Fri, 17 Nov 2023 21:01:26 -0400 Subject: [PATCH 17/23] Ability to choose the place in the list to insert new programs. --- web/directives/channel-config.js | 39 +++++++++++++++++++++--- web/directives/plex-library.js | 6 ++-- web/public/templates/channel-config.html | 3 +- web/public/templates/plex-library.html | 18 +++++++++-- 4 files changed, 55 insertions(+), 11 deletions(-) diff --git a/web/directives/channel-config.js b/web/directives/channel-config.js index 41a0f35..d8da0fe 100644 --- a/web/directives/channel-config.js +++ b/web/directives/channel-config.js @@ -11,7 +11,7 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions, get }, link: { - post: function (scope, element, attrs) { + post: function (scope, $element, attrs) { scope.screenW = 1920; scope.screenh = 1080; @@ -326,9 +326,10 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions, get duration: duration, isOffline: true } - scope.channel.programs.splice(scope.minProgramIndex, 0, program); + scope.channel.programs.splice(scope.channel.programs.length, 0, program); scope._selectedOffline = null scope._addingOffline = null; + scrollToLast(); updateChannelDuration() } @@ -1077,11 +1078,37 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions, get } } - scope.importPrograms = (selectedPrograms) => { + + function getAllMethods(object) { + + return Object.getOwnPropertyNames(object).filter(function (p) { + return typeof object[p] == 'function'; + }); + } + function scrollToLast() { + var programListElement = document.getElementById("channelConfigProgramList"); + $timeout(() => { programListElement.scrollTo(0, 2000000); }, 0) + } + scope.importPrograms = (selectedPrograms, insertPoint) => { for (let i = 0, l = selectedPrograms.length; i < l; i++) { delete selectedPrograms[i].commercials; } - scope.channel.programs = scope.channel.programs.concat(selectedPrograms) + + var programListElement = document.getElementById("channelConfigProgramList"); + if (insertPoint === "start") { + scope.channel.programs = selectedPrograms.concat(scope.channel.programs); + programListElement.scrollTo(0, 0); + } else if (insertPoint === "current") { + scope.channel.programs = [ + ...scope.channel.programs.slice(0, scope.currentStartIndex), + ...selectedPrograms, + ...scope.channel.programs.slice(scope.currentStartIndex) + ]; + } else { + scope.channel.programs = scope.channel.programs.concat(selectedPrograms) + + scrollToLast(); + } updateChannelDuration() setTimeout( () => { @@ -1093,7 +1120,9 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions, get } scope.finishRedirect = (program) => { if (scope.selectedProgram == -1) { - scope.channel.programs.splice(scope.minProgramIndex, 0, program); + scope.channel.programs.splice(scope.channel.programs.length, 0, program); + scrollToLast(); + } else { scope.channel.programs[ scope.selectedProgram ] = program; } diff --git a/web/directives/plex-library.js b/web/directives/plex-library.js index 23b89d9..153b2e2 100644 --- a/web/directives/plex-library.js +++ b/web/directives/plex-library.js @@ -6,6 +6,7 @@ module.exports = function (plex, dizquetv, $timeout, commonProgramTools) { scope: { onFinish: "=onFinish", height: "=height", + positionChoice: "=positionChoice", visible: "=visible", limit: "=limit", }, @@ -14,6 +15,7 @@ module.exports = function (plex, dizquetv, $timeout, commonProgramTools) { if ( typeof(scope.limit) == 'undefined') { scope.limit = 1000000000; } + scope.insertPoint = "end"; scope.customShows = []; scope.origins = []; scope.currentOrigin = undefined; @@ -37,7 +39,7 @@ module.exports = function (plex, dizquetv, $timeout, commonProgramTools) { updateCustomShows(); } } - scope._onFinish = (s) => { + scope._onFinish = (s, insertPoint) => { if (s.length > scope.limit) { if (scope.limit == 1) { scope.error = "Please select only one clip."; @@ -45,7 +47,7 @@ module.exports = function (plex, dizquetv, $timeout, commonProgramTools) { scope.error = `Please select at most ${scope.limit} clips.`; } } else { - scope.onFinish(s) + scope.onFinish(s, insertPoint) scope.selection = [] scope.visible = false } diff --git a/web/public/templates/channel-config.html b/web/public/templates/channel-config.html index 97dcddb..081300d 100644 --- a/web/public/templates/channel-config.html +++ b/web/public/templates/channel-config.html @@ -170,6 +170,7 @@ ng-init="setUpWatcher()" ng-if="true" ng-style="{'max-height':programmingHeight()}" + id="channelConfigProgramList" >
          - + diff --git a/web/public/templates/plex-library.html b/web/public/templates/plex-library.html index 2410b65..5435974 100644 --- a/web/public/templates/plex-library.html +++ b/web/public/templates/plex-library.html @@ -145,9 +145,21 @@
    {{error}}
    - From a8cdc6f449b245d61c2e65f684262f34c1ea8137 Mon Sep 17 00:00:00 2001 From: vexorian Date: Tue, 21 Nov 2023 14:37:45 -0400 Subject: [PATCH 18/23] Fix median condition not working when the number of filler videos in a channel is odd. --- src/helperFuncs.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/helperFuncs.js b/src/helperFuncs.js index 9ed0755..da291ce 100644 --- a/src/helperFuncs.js +++ b/src/helperFuncs.js @@ -345,8 +345,9 @@ function getMedian(channelCache, channel, fillers) { if (times.length == 0) { return null; } - quickselect(times, times.length / 2) - return times[times.length / 2]; + let m = Math.floor(times.length / 2); + quickselect(times, m) + return times[m]; } From 189a2adf4e17caf98c8c008f93ecc0f2944db36a Mon Sep 17 00:00:00 2001 From: vexorian Date: Wed, 22 Nov 2023 00:30:57 -0400 Subject: [PATCH 19/23] The registry of when a filler was last played is now persistent. --- index.js | 15 +++++- src/channel-cache.js | 33 ++++++------ src/dao/program-play-time-db.js | 90 +++++++++++++++++++++++++++++++++ src/helperFuncs.js | 14 ++--- src/video.js | 11 ++-- 5 files changed, 133 insertions(+), 30 deletions(-) create mode 100644 src/dao/program-play-time-db.js diff --git a/index.js b/index.js index da5ee00..96fe298 100644 --- a/index.js +++ b/index.js @@ -29,6 +29,7 @@ const EventService = require("./src/services/event-service"); const OnDemandService = require("./src/services/on-demand-service"); 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 onShutdown = require("node-graceful-shutdown").onShutdown; @@ -95,7 +96,19 @@ channelService = new ChannelService(channelDB); fillerDB = new FillerDB( path.join(process.env.DATABASE, 'filler') , channelService ); customShowDB = new CustomShowDB( path.join(process.env.DATABASE, 'custom-shows') ); +let programPlayTimeDB = new ProgramPlayTimeDB( path.join(process.env.DATABASE, 'play-cache') ); +async function initializeProgramPlayTimeDB() { + try { + let t0 = new Date().getTime(); + await programPlayTimeDB.load(); + let t1 = new Date().getTime(); + console.log(`Program Play Time Cache loaded in ${t1-t0} milliseconds.`); + } catch (err) { + console.log(err); + } +} +initializeProgramPlayTimeDB(); fileCache = new FileCacheService( path.join(process.env.DATABASE, 'cache') ); cacheImageService = new CacheImageService(db, fileCache); @@ -270,7 +283,7 @@ app.use('/custom.css', express.static(path.join(process.env.DATABASE, 'custom.cs app.use(api.router(db, channelService, fillerDB, customShowDB, xmltvInterval, guideService, m3uService, eventService )) app.use('/api/cache/images', cacheImageService.apiRouters()) -app.use(video.router( channelService, fillerDB, db, programmingService, activeChannelService )) +app.use(video.router( channelService, fillerDB, db, programmingService, activeChannelService, programPlayTimeDB )) app.use(hdhr.router) app.listen(process.env.PORT, () => { console.log(`HTTP server running on port: http://*:${process.env.PORT}`) diff --git a/src/channel-cache.js b/src/channel-cache.js index 302510a..9485ffb 100644 --- a/src/channel-cache.js +++ b/src/channel-cache.js @@ -1,7 +1,7 @@ const SLACK = require('./constants').SLACK; let cache = {}; -let programPlayTimeCache = {}; + let fillerPlayTimeCache = {}; let configCache = {}; let numbers = null; @@ -14,11 +14,9 @@ async function getChannelConfig(channelDB, channelId) { if (channel == null) { configCache[channelId] = []; } else { - //console.log("channel=" + JSON.stringify(channel) ); configCache[channelId] = [channel]; } } - //console.log("channel=" + JSON.stringify(configCache[channelId]).slice(0,200) ); return configCache[channelId]; } @@ -106,7 +104,7 @@ function getCurrentLineupItem(channelId, t1) { return lineupItem; } -function getKey(channelId, program) { +function getProgramKey(program) { let serverKey = "!unknown!"; if (typeof(program.serverKey) !== 'undefined') { if (typeof(program.serverKey) !== 'undefined') { @@ -117,36 +115,37 @@ function getKey(channelId, program) { if (typeof(program.key) !== 'undefined') { programKey = program.key; } - return channelId + "|" + serverKey + "|" + programKey; - + return serverKey + "|" + programKey; } + function getFillerKey(channelId, fillerId) { return channelId + "|" + fillerId; } -function recordProgramPlayTime(channelId, lineupItem, t0) { +function recordProgramPlayTime(programPlayTime, channelId, lineupItem, t0) { let remaining; if ( typeof(lineupItem.streamDuration) !== 'undefined') { remaining = lineupItem.streamDuration; } else { remaining = lineupItem.duration - lineupItem.start; } - programPlayTimeCache[ getKey(channelId, lineupItem) ] = t0 + remaining; + setProgramLastPlayTime(programPlayTime, channelId, lineupItem, t0 + remaining); if (typeof(lineupItem.fillerId) !== 'undefined') { fillerPlayTimeCache[ getFillerKey(channelId, lineupItem.fillerId) ] = t0 + remaining; } } -function getProgramLastPlayTime(channelId, program) { - let v = programPlayTimeCache[ getKey(channelId, program) ]; - if (typeof(v) === 'undefined') { - return 0; - } else { - return v; - } +function setProgramLastPlayTime(programPlayTime, channelId, lineupItem, t) { + let programKey = getProgramKey(lineupItem); + programPlayTime.update(channelId, programKey, t); +} + +function getProgramLastPlayTime(programPlayTime, channelId, program) { + let programKey = getProgramKey(program); + return programPlayTime.getProgramLastPlayTime(channelId, programKey); } function getFillerLastPlayTime(channelId, fillerId) { @@ -158,8 +157,8 @@ function getFillerLastPlayTime(channelId, fillerId) { } } -function recordPlayback(channelId, t0, lineupItem) { - recordProgramPlayTime(channelId, lineupItem, t0); +function recordPlayback(programPlayTime, channelId, t0, lineupItem) { + recordProgramPlayTime(programPlayTime, channelId, lineupItem, t0); cache[channelId] = { t0: t0, diff --git a/src/dao/program-play-time-db.js b/src/dao/program-play-time-db.js new file mode 100644 index 0000000..f285e74 --- /dev/null +++ b/src/dao/program-play-time-db.js @@ -0,0 +1,90 @@ +const path = require('path'); +var fs = require('fs'); + +class ProgramPlayTimeDB { + + constructor(dir) { + this.dir = dir; + this.programPlayTimeCache = {}; + } + + + async load() { + try { + if (! (await fs.promises.stat(this.dir)).isDirectory()) { + return; + } + } catch (err) { + return; + } + let files = await fs.promises.readdir(this.dir); + + let processSubFileName = async (fileName, subDir, subFileName) => { + try { + if (subFileName.endsWith(".json")) { + let programKey64 = subFileName.substring( + 0, + subFileName.length - 4 + ); + let programKey = Buffer.from(programKey64, 'base64') + .toString('utf-8'); + + + let filePath = path.join(subDir, subFileName); + let fileContent = await fs.promises.readFile( + filePath, 'utf-8'); + let jsonData = JSON.parse(fileContent); + let key = getKey(fileName, programKey); + this.programPlayTimeCache[ key ] = jsonData["t"] + } + } catch (err) { + console.log(`When processing ${subDir}/${subFileName}`, err); + } + } + + let processFileName = async(fileName) => { + + try { + const subDir = path.join(this.dir, fileName); + let subFiles = await fs.promises.readdir( subDir ); + + await Promise.all( subFiles.map( async subFileName => { + return processSubFileName(fileName, subDir, subFileName); + }) ); + } catch (err) { + console.log(`When processing ${subDir}`, err); + } + } + + await Promise.all( files.map(processFileName) ); + } + + getProgramLastPlayTime(channelId, programKey) { + let v = this.programPlayTimeCache[ getKey(channelId, programKey) ]; + if (typeof(v) === 'undefined') { + v = 0; + } + return v; + } + + async update(channelId, programKey, t) { + + let key = getKey(channelId, programKey); + this.programPlayTimeCache[ key ] = t; + + const channelDir = path.join(this.dir, `${channelId}`); + await fs.promises.mkdir( channelDir, { recursive: true } ); + let key64 = Buffer.from(programKey, 'utf-8').toString('base64'); + let filepath = path.join(channelDir, `${key64}.json`); + let data = {t:t}; + await fs.promises.writeFile(filepath, JSON.stringify(data), 'utf-8'); + } + +} + +function getKey(channelId, programKey) { + return channelId + "|" + programKey; +} + + +module.exports = ProgramPlayTimeDB; \ No newline at end of file diff --git a/src/helperFuncs.js b/src/helperFuncs.js index da291ce..a893b14 100644 --- a/src/helperFuncs.js +++ b/src/helperFuncs.js @@ -62,7 +62,7 @@ function getCurrentProgramAndTimeElapsed(date, channel) { return { program: channel.programs[currentProgramIndex], timeElapsed: timeElapsed, programIndex: currentProgramIndex } } -function createLineup(obj, channel, fillers, isFirst) { +function createLineup(programPlayTime, obj, channel, fillers, isFirst) { let timeElapsed = obj.timeElapsed // Start time of a file is never consistent unless 0. Run time of an episode can vary. // When within 30 seconds of start time, just make the time 0 to smooth things out @@ -97,7 +97,7 @@ function createLineup(obj, channel, fillers, isFirst) { if ( (channel.offlineMode === 'clip') && (channel.fallback.length != 0) ) { special = JSON.parse(JSON.stringify(channel.fallback[0])); } - let randomResult = pickRandomWithMaxDuration(channel, fillers, remaining + (isFirst? (7*24*60*60*1000) : 0) ); + let randomResult = pickRandomWithMaxDuration(programPlayTime, channel, fillers, remaining + (isFirst? (7*24*60*60*1000) : 0) ); filler = randomResult.filler; if (filler == null && (typeof(randomResult.minimumWait) !== undefined) && (remaining > randomResult.minimumWait) ) { remaining = randomResult.minimumWait; @@ -179,7 +179,7 @@ function weighedPick(a, total) { return random.bool(a, total); } -function pickRandomWithMaxDuration(channel, fillers, maxDuration) { +function pickRandomWithMaxDuration(programPlayTime, channel, fillers, maxDuration) { let list = []; for (let i = 0; i < fillers.length; i++) { list = list.concat(fillers[i].content); @@ -196,7 +196,7 @@ function pickRandomWithMaxDuration(channel, fillers, maxDuration) { let listM = 0; let fillerId = undefined; - let median = getMedian(channelCache, channel, fillers); + let median = getMedian(programPlayTime, channelCache, channel, fillers); for (let medianCheck = 1; medianCheck >= 0; medianCheck--) { for (let j = 0; j < fillers.length; j++) { @@ -208,7 +208,7 @@ function pickRandomWithMaxDuration(channel, fillers, maxDuration) { let clip = list[i]; // a few extra milliseconds won't hurt anyone, would it? dun dun dun if (clip.duration <= maxDuration + SLACK ) { - let t1 = channelCache.getProgramLastPlayTime( channel.number, clip ); + let t1 = channelCache.getProgramLastPlayTime(programPlayTime, channel.number, clip ); if ( (medianCheck==1) && (t1 > median) ) { continue; } @@ -332,13 +332,13 @@ function getWatermark( ffmpegSettings, channel, type) { } -function getMedian(channelCache, channel, fillers) { +function getMedian(programPlayTime, channelCache, channel, fillers) { let times = []; for (let j = 0; j < fillers.length; j++) { list = fillers[j].content; for (let i = 0; i < list.length; i++) { let clip = list[i]; - let t = channelCache.getProgramLastPlayTime( channel.number, clip); + let t = channelCache.getProgramLastPlayTime(programPlayTime, channel.number, clip); times.push(t); } } diff --git a/src/video.js b/src/video.js index 11b02bc..2854880 100644 --- a/src/video.js +++ b/src/video.js @@ -18,7 +18,7 @@ async function shutdown() { stopPlayback = true; } -function video( channelService, fillerDB, db, programmingService, activeChannelService ) { +function video( channelService, fillerDB, db, programmingService, activeChannelService, programPlayTimeDB ) { var router = express.Router() router.get('/setup', (req, res) => { @@ -232,7 +232,8 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS if ( !(prog.program.isOffline) || (prog.program.type != 'redirect') ) { break; } - channelCache.recordPlayback( brandChannel.number, t0, { + channelCache.recordPlayback(programPlayTimeDB, + brandChannel.number, t0, { /*type: 'offline',*/ title: 'Error', err: Error("Recursive channel redirect found"), @@ -299,7 +300,7 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS } let fillers = await fillerDB.getFillersFromChannel(brandChannel); try { - let lineup = helperFuncs.createLineup(prog, brandChannel, fillers, isFirst) + let lineup = helperFuncs.createLineup(programPlayTimeDB, prog, brandChannel, fillers, isFirst) lineupItem = lineup.shift(); } catch (err) { console.log("Error when attempting to pick video: " +err.stack); @@ -331,7 +332,7 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS lineupItem.streamDuration = Math.min(u2, u); upperBound = lineupItem.streamDuration; } - channelCache.recordPlayback( redirectChannels[i].number, t0, lineupItem ); + channelCache.recordPlayback( programPlayTimeDB, redirectChannels[i].number, t0, lineupItem ); } } @@ -354,7 +355,7 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS console.log("========================================================="); if (! isLoading && ! isBetween) { - channelCache.recordPlayback(channel.number, t0, lineupItem); + channelCache.recordPlayback(programPlayTimeDB, channel.number, t0, lineupItem); } if (wereThereTooManyAttempts(session, lineupItem)) { console.error("There are too many attempts to play the same item in a short period of time, playing the error stream instead."); From c2bb2c8df1ddb5a4b9d345e118a9a16bea604d73 Mon Sep 17 00:00:00 2001 From: vexorian Date: Thu, 23 Nov 2023 11:46:15 -0400 Subject: [PATCH 20/23] Make median logic be based on the specific filler list instead of the whole combined fillers --- src/helperFuncs.js | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/helperFuncs.js b/src/helperFuncs.js index a893b14..1d905f9 100644 --- a/src/helperFuncs.js +++ b/src/helperFuncs.js @@ -6,6 +6,7 @@ module.exports = { } let channelCache = require('./channel-cache'); +const INFINITE_TIME = new Date().getTime() + 10*365*24*60*60*1000; //10 years from the initialization of the server. I dunno, I just wanted it to be a high time without it stopping being human readable if converted to date. const SLACK = require('./constants').SLACK; const randomJS = require("random-js"); const quickselect = require("quickselect"); @@ -196,20 +197,33 @@ function pickRandomWithMaxDuration(programPlayTime, channel, fillers, maxDuratio let listM = 0; let fillerId = undefined; - let median = getMedian(programPlayTime, channelCache, channel, fillers); - for (let medianCheck = 1; medianCheck >= 0; medianCheck--) { for (let j = 0; j < fillers.length; j++) { list = fillers[j].content; let pickedList = false; let n = 0; + let maximumPlayTimeAllowed = INFINITE_TIME; + if (medianCheck==1) { + //calculate the median + let median = getFillerMedian(programPlayTime, channel, fillers[j]); + if (median > 0) { + maximumPlayTimeAllowed = median - 1; + // allow any clip with a play time that's less than the median. + } else { + // initially all times are 0, so if the median is 0, all of those + // are allowed. + maximumPlayTimeAllowed = 0; + } + } + + for (let i = 0; i < list.length; i++) { let clip = list[i]; // a few extra milliseconds won't hurt anyone, would it? dun dun dun if (clip.duration <= maxDuration + SLACK ) { let t1 = channelCache.getProgramLastPlayTime(programPlayTime, channel.number, clip ); - if ( (medianCheck==1) && (t1 > median) ) { + if (t1 > maximumPlayTimeAllowed) { continue; } let timeSince = ( (t1 == 0) ? D : (t0 - t1) ); @@ -332,18 +346,19 @@ function getWatermark( ffmpegSettings, channel, type) { } -function getMedian(programPlayTime, channelCache, channel, fillers) { +function getFillerMedian(programPlayTime, channel, filler) { + let times = []; - for (let j = 0; j < fillers.length; j++) { - list = fillers[j].content; - for (let i = 0; i < list.length; i++) { - let clip = list[i]; - let t = channelCache.getProgramLastPlayTime(programPlayTime, channel.number, clip); - times.push(t); - } + list = filler.content; + for (let i = 0; i < list.length; i++) { + let clip = list[i]; + let t = channelCache.getProgramLastPlayTime(programPlayTime, channel.number, clip); + times.push(t); } - if (times.length == 0) { - return null; + + if (times.length <= 1) { + //if there are too few elements, the protection is not helpful. + return INFINITE_TIME; } let m = Math.floor(times.length / 2); quickselect(times, m) From c427322f3748d374aeb2b889d58763c7f1972eb6 Mon Sep 17 00:00:00 2001 From: vexorian Date: Sun, 26 Nov 2023 21:02:36 -0400 Subject: [PATCH 21/23] Disable interlude in the same cases where loading screen is disabled. --- src/video.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/video.js b/src/video.js index 2854880..c21481c 100644 --- a/src/video.js +++ b/src/video.js @@ -582,12 +582,14 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS let sessionId = StreamCount++; let audioOnly = ("true" == req.query.audioOnly); - if ( - (ffmpegSettings.enableFFMPEGTranscoding === true) + let transcodingEnabled = (ffmpegSettings.enableFFMPEGTranscoding === true) && (ffmpegSettings.normalizeVideoCodec === true) && (ffmpegSettings.normalizeAudioCodec === true) && (ffmpegSettings.normalizeResolution === true) - && (ffmpegSettings.normalizeAudio === true) + && (ffmpegSettings.normalizeAudio === true); + + if ( + transcodingEnabled && (audioOnly !== true) /* loading screen is pointless in audio mode (also for some reason it makes it fail when codec is aac, and I can't figure out why) */ && (stepNumber == 0) ) { @@ -598,13 +600,17 @@ function video( channelService, fillerDB, db, programmingService, activeChannelS if (stepNumber == 0) { data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&first=1&session=${sessionId}&audioOnly=${audioOnly}'\n` - data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&between=1&session=${sessionId}&audioOnly=${audioOnly}'\n`; + if (transcodingEnabled && (audioOnly !== true)) { + data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&between=1&session=${sessionId}&audioOnly=${audioOnly}'\n`; + } remaining--; } for (var i = 0; i < remaining; i++) { data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&session=${sessionId}&audioOnly=${audioOnly}'\n` - data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&between=1&session=${sessionId}&audioOnly=${audioOnly}'\n` + if (transcodingEnabled && (audioOnly !== true) ) { + data += `file 'http://localhost:${process.env.PORT}/stream?channel=${channelNum}&between=1&session=${sessionId}&audioOnly=${audioOnly}'\n` + } } res.send(data) From acc49fcd349f3c8d84ed921c0c24596041569092 Mon Sep 17 00:00:00 2001 From: vexorian Date: Sun, 26 Nov 2023 22:14:33 -0400 Subject: [PATCH 22/23] Refresh button for the library. --- web/public/templates/plex-library.html | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/web/public/templates/plex-library.html b/web/public/templates/plex-library.html index 5435974..adc2f58 100644 --- a/web/public/templates/plex-library.html +++ b/web/public/templates/plex-library.html @@ -37,8 +37,12 @@
    - -
    +
    • @@ -127,6 +131,7 @@
    +
    Selected Items
    From 393de7429cc54690e455fd187e762b5e9fe51353 Mon Sep 17 00:00:00 2001 From: vexorian Date: Sun, 26 Nov 2023 22:15:22 -0400 Subject: [PATCH 23/23] 1.5.1 --- README.md | 2 +- src/constants.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 095f201..096bc3c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# dizqueTV 1.5.1-development +# dizqueTV 1.5.1 ![Discord](https://img.shields.io/discord/711313431457693727?logo=discord&logoColor=fff&style=flat-square) ![GitHub top language](https://img.shields.io/github/languages/top/vexorian/dizquetv?logo=github&style=flat-square) ![Docker Pulls](https://img.shields.io/docker/pulls/vexorian/dizquetv?logo=docker&logoColor=fff&style=flat-square) Create live TV channel streams from media on your Plex servers. diff --git a/src/constants.js b/src/constants.js index 6a9619c..711202a 100644 --- a/src/constants.js +++ b/src/constants.js @@ -35,5 +35,5 @@ module.exports = { // staying active, it checks every 5 seconds PLAYED_MONITOR_CHECK_FREQUENCY: 5*1000, - VERSION_NAME: "1.5.1-development" + VERSION_NAME: "1.5.1" }