lrkmusic-ytpro 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/bgplay.js +186 -108
  2. package/package.json +2 -2
  3. package/script.js +1534 -1524
package/bgplay.js CHANGED
@@ -1,17 +1,17 @@
1
1
  /***** lrkmusic-ytpro bgplay *****
2
2
  Author: Satya Prakash
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  */
5
5
 
6
6
  if (typeof MediaMetadata === 'undefined') {
7
- window.MediaMetadata = class {
8
- constructor(data = {}) {
9
- this.title = data.title || '';
10
- this.artist = data.artist || '';
11
- this.album = data.album || '';
12
- this.artwork = data.artwork || [];
13
- }
14
- };
7
+ window.MediaMetadata = class {
8
+ constructor(data = {}) {
9
+ this.title = data.title || '';
10
+ this.artist = data.artist || '';
11
+ this.album = data.album || '';
12
+ this.artwork = data.artwork || [];
13
+ }
14
+ };
15
15
 
16
16
  }
17
17
 
@@ -19,79 +19,78 @@ this.artwork = data.artwork || [];
19
19
 
20
20
  if (!('mediaSession' in navigator)) {
21
21
 
22
- window.handlers = {};
23
- window.serviceRunning=false;
24
-
22
+ window.handlers = {};
23
+ window.serviceRunning = false;
25
24
 
26
25
 
27
26
 
28
- let _state = 'none';
29
- let _metadata = null;
30
27
 
31
- Object.defineProperty(navigator, 'mediaSession', {
32
- value: {},
33
- configurable: true
34
- });
28
+ let _state = 'none';
29
+ let _metadata = null;
35
30
 
36
- Object.defineProperty(navigator.mediaSession, 'metadata', {
37
- get() {
38
- return _metadata;
39
- },
40
- set(value) {
41
- //console.log("metadata set:", value);
42
- bgPlay(value);
43
- _metadata = value;
44
- },
45
- configurable: true
46
- });
31
+ Object.defineProperty(navigator, 'mediaSession', {
32
+ value: {},
33
+ configurable: true
34
+ });
47
35
 
36
+ Object.defineProperty(navigator.mediaSession, 'metadata', {
37
+ get() {
38
+ return _metadata;
39
+ },
40
+ set(value) {
41
+ //console.log("metadata set:", value);
42
+ bgPlay(value);
43
+ _metadata = value;
44
+ },
45
+ configurable: true
46
+ });
48
47
 
49
48
 
50
49
 
51
- navigator.mediaSession.setActionHandler = (action, handler) => {
52
50
 
53
- if (typeof handler === 'function') {
54
- handlers[action] = handler;
55
- }
51
+ navigator.mediaSession.setActionHandler = (action, handler) => {
56
52
 
57
- //console.log(action,handler)
53
+ if (typeof handler === 'function') {
54
+ handlers[action] = handler;
55
+ }
58
56
 
57
+ //console.log(action,handler)
59
58
 
60
- };
61
59
 
60
+ };
62
61
 
63
62
 
64
63
 
65
64
 
66
65
 
67
- Object.defineProperty(navigator.mediaSession, 'playbackState', {
68
- get() {
69
- return _state;
70
- },
71
- set(value) {
66
+ Object.defineProperty(navigator.mediaSession, 'playbackState', {
67
+ get() {
68
+ return _state;
69
+ },
70
+ set(value) {
72
71
 
73
- //console.log("Custom playbackState set to:", value);
72
+ //console.log("Custom playbackState set to:", value);
74
73
 
75
74
 
76
- _state = value;
75
+ _state = value;
77
76
 
78
77
 
79
- var ytproAud = getMediaElement();
78
+ var ytproAud = getMediaElement();
80
79
 
81
- if (value === 'playing' && ytproAud) {
82
- setTimeout(()=>{Android.bgPlay(ytproAud.currentTime*1000);},100);
83
- } else if (value === 'paused' && (pauseAllowed || PIPause) && ytproAud) {
84
- setTimeout(()=>{Android.bgPause(ytproAud.currentTime*1000);},100);
85
- }
86
- // Do NOT call bgStop() when user goes back to home – keep video running like YouTube app.
87
- // Service stops only when YouTube screen is closed (app onDestroy) or user stops from notification.
88
- // Removed: else if(value === "none" && !on watch/shorts){ Android.bgStop(); serviceRunning=false; }
80
+ if (value === 'playing' && ytproAud) {
81
+ setTimeout(() => { Android.bgPlay(ytproAud.currentTime * 1000); }, 100);
82
+ } else if (value === 'paused' && (pauseAllowed || PIPause) && ytproAud) {
83
+ setTimeout(() => { Android.bgPause(ytproAud.currentTime * 1000); }, 100);
84
+ }
85
+ // Do NOT call bgStop() when user goes back to home – keep video running like YouTube app.
86
+ // Service stops only when YouTube screen is closed (app onDestroy) or user stops from notification.
87
+ // Removed: else if(value === "none" && !on watch/shorts){ Android.bgStop(); serviceRunning=false; }
89
88
 
90
89
 
91
90
 
92
- },
93
- configurable: true
94
- });
91
+ },
92
+ configurable: true
93
+ });
95
94
 
96
95
 
97
96
 
@@ -101,7 +100,7 @@ configurable: true
101
100
  try {
102
101
  var origSet = navigator.mediaSession.setActionHandler;
103
102
  if (origSet) {
104
- navigator.mediaSession.setActionHandler = function(action, handler) {
103
+ navigator.mediaSession.setActionHandler = function (action, handler) {
105
104
  if (typeof handler === 'function') window.handlers[action] = handler;
106
105
  return origSet.call(navigator.mediaSession, action, handler);
107
106
  };
@@ -111,14 +110,93 @@ configurable: true
111
110
  var origMetaSet = desc.set;
112
111
  Object.defineProperty(navigator.mediaSession, 'metadata', {
113
112
  get: desc.get,
114
- set: function(v) { origMetaSet.call(navigator.mediaSession, v); bgPlay(v); },
113
+ set: function (v) { origMetaSet.call(navigator.mediaSession, v); bgPlay(v); },
115
114
  configurable: true, enumerable: desc.enumerable
116
115
  });
117
116
  }
118
- } catch (e) {}
117
+ } catch (e) { }
119
118
  }
120
119
 
121
120
 
121
+ /*=========================================================
122
+ FIX 1: Visibilitychange auto-resume guard
123
+ When screen locks / app goes to background, YouTube's
124
+ player pauses. We detect this and force-resume playback.
125
+ =========================================================*/
126
+ document.addEventListener('visibilitychange', function () {
127
+ if (document.hidden && window.serviceRunning) {
128
+ // Set pauseAllowed false so pause override blocks YouTube's auto-pause
129
+ window.pauseAllowed = false;
130
+ var el = getMediaElement();
131
+ if (el && !el.ended) {
132
+ // Multiple retries because YouTube tries to pause at different timings
133
+ setTimeout(function () { try { el.play().catch(function () { }); } catch (e) { } }, 300);
134
+ setTimeout(function () { try { el.play().catch(function () { }); } catch (e) { } }, 800);
135
+ setTimeout(function () { try { el.play().catch(function () { }); } catch (e) { } }, 1500);
136
+ setTimeout(function () { try { el.play().catch(function () { }); } catch (e) { } }, 3000);
137
+ }
138
+ } else if (!document.hidden) {
139
+ // Page visible again — allow normal pause behavior
140
+ window.pauseAllowed = true;
141
+ }
142
+ });
143
+
144
+
145
+ /*=========================================================
146
+ FIX 2: Background auto-play-next on video ended
147
+ Uses JS-level handlers.nexttrack() instead of DOM clicks
148
+ so it works reliably when screen is locked / bg.
149
+ =========================================================*/
150
+ function setupBgAutoPlayNext() {
151
+ var el = getMediaElement();
152
+ if (el && !el._bgAutoNextSetup) {
153
+ el._bgAutoNextSetup = true;
154
+ el.addEventListener('ended', function () {
155
+ // Only auto-next if we're on a watch/shorts/music page
156
+ var href = window.location.href;
157
+ if (!(href.indexOf("youtube.com/watch") > -1 || href.indexOf("youtube.com/shorts") > -1 || href.indexOf("music.youtube.com") > -1)) return;
158
+
159
+ if (handlers && typeof handlers.nexttrack === 'function') {
160
+ setTimeout(function () { handlers.nexttrack(); }, 500);
161
+ } else {
162
+ // Fallback: try clicking next button via DOM
163
+ setTimeout(function () {
164
+ try {
165
+ var nextBtn = document.querySelector('[aria-label*="Next"]') ||
166
+ document.querySelector('button[aria-label*="Next"]') ||
167
+ document.querySelector('.ytp-next-button');
168
+ if (nextBtn) nextBtn.click();
169
+ } catch (e) { }
170
+ }, 500);
171
+ }
172
+ });
173
+ }
174
+ }
175
+
176
+ // Re-setup on mutations (new video element created)
177
+ if (typeof MutationObserver !== 'undefined') {
178
+ var _bgNextObserver = new MutationObserver(function () { setupBgAutoPlayNext(); });
179
+ _bgNextObserver.observe(document.documentElement, { childList: true, subtree: true });
180
+ }
181
+ // Also try immediately
182
+ setTimeout(setupBgAutoPlayNext, 2000);
183
+ setTimeout(setupBgAutoPlayNext, 5000);
184
+
185
+
186
+ /*=========================================================
187
+ FIX 3: Watchdog timer — re-play if unexpectedly paused
188
+ Catches edge cases where notifications, SMS, or other
189
+ audio focus events cause the WebView media to pause.
190
+ Only active when bg service is running.
191
+ =========================================================*/
192
+ setInterval(function () {
193
+ if (window.serviceRunning && !window.pauseAllowed && !window.PIPause) {
194
+ var el = getMediaElement();
195
+ if (el && el.paused && !el.ended && el.readyState >= 2) {
196
+ try { el.play().catch(function () { }); } catch (e) { }
197
+ }
198
+ }
199
+ }, 3000);
122
200
 
123
201
 
124
202
 
@@ -129,50 +207,50 @@ function getMediaElement() {
129
207
  return el || null;
130
208
  }
131
209
 
132
- async function bgPlay(info){
210
+ async function bgPlay(info) {
133
211
 
134
212
 
135
- if(!(window.location.href.indexOf("youtube.com/watch") > -1 || window.location.href.indexOf("youtube.com/shorts") > -1 || window.location.href.indexOf("music.youtube.com") > -1 )) return;
213
+ if (!(window.location.href.indexOf("youtube.com/watch") > -1 || window.location.href.indexOf("youtube.com/shorts") > -1 || window.location.href.indexOf("music.youtube.com") > -1)) return;
136
214
 
137
215
 
138
- if(!info) return;
216
+ if (!info) return;
139
217
 
140
218
 
141
- var ytproAud = getMediaElement();
219
+ var ytproAud = getMediaElement();
142
220
 
143
221
 
144
- if(!ytproAud) return;
222
+ if (!ytproAud) return;
145
223
 
146
224
 
147
- var iconBase64="iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=";
225
+ var iconBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=";
148
226
 
149
227
 
150
- var img = new Image();
151
- img.crossOrigin="anonymous";
152
- img.src=info?.artwork?.[0]?.src;
228
+ var img = new Image();
229
+ img.crossOrigin = "anonymous";
230
+ img.src = info?.artwork?.[0]?.src;
153
231
 
154
232
 
155
- var canvas = document.createElement('canvas');
156
- canvas.style.width = "1600px";
157
- canvas.style.height = "900px";
158
- canvas.style.background="black";
159
- var context = canvas.getContext('2d');
233
+ var canvas = document.createElement('canvas');
234
+ canvas.style.width = "1600px";
235
+ canvas.style.height = "900px";
236
+ canvas.style.background = "black";
237
+ var context = canvas.getContext('2d');
160
238
 
161
- canvas.width = 160;
162
- canvas.height = 90;
239
+ canvas.width = 160;
240
+ canvas.height = 90;
163
241
 
164
- //var z=performance.now();
242
+ //var z=performance.now();
165
243
 
166
244
 
167
- await new Promise((res,rej)=>{
168
- img.onload=()=>res();
169
- });
245
+ await new Promise((res, rej) => {
246
+ img.onload = () => res();
247
+ });
170
248
 
171
249
 
172
- try{
173
- context.drawImage(img, 0,0 ,160,90);
174
- iconBase64 = canvas.toDataURL('image/png',1.0);
175
- }catch{}
250
+ try {
251
+ context.drawImage(img, 0, 0, 160, 90);
252
+ iconBase64 = canvas.toDataURL('image/png', 1.0);
253
+ } catch { }
176
254
 
177
255
 
178
256
 
@@ -185,15 +263,15 @@ iconBase64 = canvas.toDataURL('image/png',1.0);
185
263
 
186
264
 
187
265
 
188
- if(window.serviceRunning){
189
- setTimeout(()=>{Android.bgUpdate(iconBase64.replace("data:image/png;base64,", ""),info.title,info.artist,ytproAud.duration*1000);},50);
190
- setTimeout(()=>{Android.bgPlay(ytproAud.currentTime*1000);},100);
191
- }
192
- else{
193
- window.serviceRunning=true;
194
- setTimeout(()=>{Android.bgStart(iconBase64.replace("data:image/png;base64,", ""),info.title,info.artist,ytproAud.duration*1000);},50);
195
- setTimeout(()=>{Android.bgPlay(ytproAud.currentTime*1000);},100);
196
- }
266
+ if (window.serviceRunning) {
267
+ setTimeout(() => { Android.bgUpdate(iconBase64.replace("data:image/png;base64,", ""), info.title, info.artist, ytproAud.duration * 1000); }, 50);
268
+ setTimeout(() => { Android.bgPlay(ytproAud.currentTime * 1000); }, 100);
269
+ }
270
+ else {
271
+ window.serviceRunning = true;
272
+ setTimeout(() => { Android.bgStart(iconBase64.replace("data:image/png;base64,", ""), info.title, info.artist, ytproAud.duration * 1000); }, 50);
273
+ setTimeout(() => { Android.bgPlay(ytproAud.currentTime * 1000); }, 100);
274
+ }
197
275
 
198
276
 
199
277
 
@@ -225,32 +303,32 @@ setTimeout(()=>{Android.bgPlay(ytproAud.currentTime*1000);},100);
225
303
 
226
304
 
227
305
  /*When user hits the notification*/
228
- function seekTo(t){
229
- handlers.seekto({ seekTime: t/1000 });
306
+ function seekTo(t) {
307
+ handlers.seekto({ seekTime: t / 1000 });
230
308
  }
231
309
 
232
310
  /*Daamm , its play*/
233
- function playVideo(){
311
+ function playVideo() {
234
312
 
235
313
 
236
- if(!pauseAllowed){
237
- window.PIPause = false;
238
- navigator.mediaSession.playbackState = 'playing';
239
- }
314
+ if (!pauseAllowed) {
315
+ window.PIPause = false;
316
+ navigator.mediaSession.playbackState = 'playing';
317
+ }
240
318
 
241
- handlers.play();
319
+ handlers.play();
242
320
  }
243
321
 
244
322
  /*Daamm , its pause*/
245
- function pauseVideo(){
323
+ function pauseVideo() {
246
324
 
247
325
 
248
326
 
249
- if(!pauseAllowed){
250
- window.PIPause=true;
251
- navigator.mediaSession.playbackState = 'paused';
252
- }
253
- handlers.pause();
327
+ if (!pauseAllowed) {
328
+ window.PIPause = true;
329
+ navigator.mediaSession.playbackState = 'paused';
330
+ }
331
+ handlers.pause();
254
332
 
255
333
 
256
334
 
@@ -260,14 +338,14 @@ handlers.pause();
260
338
 
261
339
 
262
340
  /*Alexa , play da next song*/
263
- async function playNext(){
264
- handlers.nexttrack();
341
+ async function playNext() {
342
+ handlers.nexttrack();
265
343
  }
266
344
 
267
345
 
268
346
 
269
347
 
270
348
  /*Alexa , play the f**ng song once again */
271
- function playPrev(){
272
- handlers.previoustrack();
349
+ function playPrev() {
350
+ handlers.previoustrack();
273
351
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lrkmusic-ytpro",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "you can find the source code at",
5
5
  "main": "script.js",
6
6
  "scripts": {
@@ -14,4 +14,4 @@
14
14
  "publishConfig": {
15
15
  "access": "public"
16
16
  }
17
- }
17
+ }