PageSourceSearch

https://shop.holysoft.de/js/plyrsetup.js?v=1772138829

js holysoft.de collected 2026-10-02 12:50:06 UTC 14,760 bytes, 506 lines download raw bytes

1'use strict';
2var player=false;
3var player_window=false;
4var pDataTransfer="OPENER";
5var killswitch=false;
6var player_state="";
7var wurl="20210406";
8// Set by PHP (inc.audioplayer / index): live = https://media.holysoft.de/, local = same origin or WEBSTREAM_BASE_LOCAL
9var webstream_base = (typeof window.HolyShopWebstreamBase !== 'undefined' ? window.HolyShopWebstreamBase : 'https://media.holysoft.de/');
10
11function isSafariMacOS() {
12  const ua = navigator.userAgent;
13
14  const isSafari = ua.includes('Safari') &&
15                   !ua.includes('Chrome') &&
16                   !ua.includes('Chromium') &&
17                   !ua.includes('CriOS') &&
18                   !ua.includes('Edg') &&
19                   !ua.includes('OPR');
20
21  const isMac = ua.includes('Macintosh');
22
23  return isSafari && isMac;
24}
25
26$.when($.ready).then( ()=> {
27	var resumedata=false;
28	if(resumedata=readCookieData('holyplayer')) {
29		if(resumedata.pid>0) {
30			
31		}
32		else {
33			resumedata=false;
34			Cookies.remove('holyplayer');
35		}
36	}
37	if(typeof show_inline_player !== 'undefined' && show_inline_player) {
38		$('.hsp_play_button').on('click keydown', (e)=> {
39			if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return;
40			if (e.type === 'keydown') e.preventDefault();
41			playStream(e.currentTarget);
42		});
43		if(resumedata) {
44			playStream(false,resumedata);
45		}
46	}
47	else {
48		$('.hsp_play_button').on('click keydown', (e)=> {
49			if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return;
50			if (e.type === 'keydown') e.preventDefault();
51			var element=e.currentTarget;
52			var pData=createPData(element);
53			pDataTransfer=pData;
54			openPlayer();
55		});
56		if(window.opener && window.opener.pDataTransfer!==false) {
57			var pData=window.opener.pDataTransfer;
58			window.opener.pDataTransfer=false;
59			playStream(pData,resumedata);
60		}
61		else if(document.body.id=="windowplayer" && resumedata) {
62			playStream(false,resumedata);
63		}
64	}
65});
66
67function createPData(element,resumedata=false) {
68	var pData={};
69	var pDataResume={};
70	if(element!==false) {
71		var pData={
72			title:$(element).data('title'),
73			serie:$(element).data('serie'),
74			episode:$(element).data('episode'),
75			cover:$(element).data('cover'),
76			prod_id:$(element).data('fileid'),
77			preview:$(element).data('preview'),
78			start_playing:true,
79			progress:false,
80			targetTime:-1,
81			curTime:0,
82			lastTime:0,
83		}
84		if(pData.preview==1) {
85			pData.title+=' Hörprobe';
86		}
87		pData= {
88			...pData,
89			full_title:pData.serie+' '+pData.episode+' '+pData.title,
90		}
91	}
92	if(resumedata!==false && isObjectHS(resumedata)) {
93		var pDataResume={
94			title:resumedata.t,
95			serie:resumedata.s,
96			episode:resumedata.e,
97			cover:resumedata.c,
98			start_playing:resumedata.pl,
99			prod_id:resumedata.pid,
100			preview:resumedata.prvw,
101			curTime:0,
102			targetTime:resumedata.ct,
103			lastTime:0,
104		}		
105		pDataResume= {
106			...pDataResume,
107			full_title:pDataResume.serie+' '+pDataResume.episode+' '+pDataResume.title,
108		}
109		pData= {
110			...pData,
111			full_title:pData.serie+' '+pData.episode+' '+pData.title,
112		}
113		if(element===false || pData.prod_id<1 || pDataResume.prod_id==pData.prod_id) {
114			pData=pDataResume;
115		}
116	}
117	pData= {
118		...pData,
119		saveTimeInterval:1,
120	}
121	return pData;
122}
123
124function unregisterPlayer(player,clear_cookie=false,hideactions=true,wait_for_close=false) {
125	var player=player;
126	var grace_timer=800;
127	return new Promise(async (resolve,reject)=> {
128		if(player) {
129			try {
130				player.stop();
131			} catch (e) {
132				// ignore if already stopped/destroyed
133			}
134			if(hideactions) {
135				$('#topPlayer').hide();			
136			}
137			try {
138				await player.destroy();
139			} catch (e) {
140				// ignore if already destroyed
141			}
142			if(clear_cookie) {
143				Cookies.remove('holyplayer');
144			}
145			player_state="destroyed";
146			player=false;
147		}
148		if(wait_for_close) {
149			setTimeout(function(){
150				resolve(true);
151			}, grace_timer);
152		}
153		else {
154			resolve(true);
155		}
156	});
157}
158
159const getConstructor = (input) => (input !== null && typeof input !== 'undefined' ? input.constructor : null);
160const isNullOrUndefined = (input) => input === null || typeof input === 'undefined';
161const isString = (input) => getConstructor(input) === String;
162const isArray = (input) => Array.isArray(input);
163const isNodeList = (input) => input instanceof NodeList;
164const isObject = (input) => getConstructor(input) === Object;
165
166function urlexists(url, cb) {
167	$.ajax({
168      url: url,
169      dataType: 'text',
170      type: 'GET',
171      xhrFields: {
172        withCredentials: true
173      },
174      timeout: 3000,
175      complete: function (xhr) {
176      if (typeof cb === 'function')
177         cb.apply(this, [xhr.status]);
178      },
179      error: function (xhr) {
180         cb.apply(this, [408]);
181      }
182	});
183}
184
185function isEmpty(input) {
186  return isNullOrUndefined(input) ||
187  ((isString(input) || isArray(input) || isNodeList(input)) && !input.length) ||
188  (isObject(input) && !Object.keys(input).length);
189}
190function debounce(func, wait, immediate) {
191  var timeout;
192  return function() {
193    var context = this,
194      args = arguments;
195    var callNow = immediate && !timeout;
196    clearTimeout(timeout);
197    timeout = setTimeout(function() {
198      timeout = null;
199      if (!immediate) {
200        func.apply(context, args);
201      }
202    }, wait);
203    if (callNow) func.apply(context, args);
204  }
205}
206function throttle(func, timeFrame) {
207  var lastTime = new Date();
208  return function () {
209	  var context = this, args = arguments;
210      var now = new Date();
211      if (now - lastTime >= timeFrame) {
212          func.apply(context, args);
213          lastTime = now;
214      }
215  };
216}
217
218function plyrSeekAction(target,player) {
219	// Seek Code
220	const seek = target;
221	// If it exists, use seek-value instead of "value" for consistency with tooltip time (#954)
222	let seekTo = seek.getAttribute('seek-value');
223	if (isEmpty(seekTo)) {
224		seekTo = seek.value;
225	}	
226	seek.removeAttribute('seek-value');
227	player.currentTime = (seekTo / seek.max) * player.duration;
228}
229
230var debouncedPlyrSeekAction = debounce(function (e,player) {
231	plyrSeekAction(e,player,true);
232},250);
233
234function readCookieData(cookieName) {
235	
236	let data=Cookies.get(cookieName);
237	if(data && data.length>0) {
238		return JSON.parse(data);
239	}
240	return false;
241}
242function writeCookieData(cookieName,data) {
243	let expireIn5Minutes=1/24/12;
244	let json_data=JSON.stringify(data);
245	Cookies.set(cookieName,json_data,{expires:expireIn5Minutes,sameSite: 'Strict'});	
246	return true;
247}
248function isObjectHS(objValue,test_constructor=true) {
249	if(test_constructor) {
250		return objValue && typeof objValue === 'object' && objValue.constructor === Object;
251	}
252	else {
253		return objValue && typeof objValue === 'object';
254	}
255}
256
257
258function openPlayer() {
259   let width = 376;
260   let height = 392;
261   if(isSafariMacOS()) {
262      const chromeHeight = 28;
263      const chromeWidth = 20;
264      width += chromeWidth;
265      height += chromeHeight;
266   }
267   
268	var player_window=window.open(base+"audioplayer/", "holyshopplayer", "toolbar=no,scrollbars=no,resizable=no,top=200,left=200,width="+width+",height="+height); 
269}
270
271function initPlayer(pData) {
272	unregisterPlayer(player,true,true);
273
274	var initTime = Date.now();
275
276	// BFSG/WCAG 2.1 AA Compliance: Announce player initialization to screen readers
277	announceToScreenReader('Player wird initialisiert...');
278	
279	player = new Plyr('#hspplayer', {
280		debug: false,
281		controls: [
282			 // 'rewind', // Rewind by the seek time (default 10 seconds)
283			 'play', // Play/pause playback
284			 // 'fast-forward', // Fast forward by the seek time (default 10 seconds)
285			 'progress', // The progress bar and scrubber for playback and buffering
286			 'current-time', // The current time of playback
287			 'duration', // The full duration of the media
288			 'mute', // Toggle mute
289			 'volume', // Volume control
290		],
291		listeners: {
292			seek: function (e) {
293				e.preventDefault() // required on v2
294				
295				var target=e.currentTarget;
296
297				debouncedPlyrSeekAction(target,player);
298			
299				return false    // required on v3
300			}
301		},
302	});
303	player_state="init";
304
305	player.source={
306	  type: 'audio',
307	  title: pData.full_title,
308	  sources: [
309		 {
310			src: webstream_base+'webstream.php?1='+wurl+'&2='+pData.prod_id+'&3='+pData.preview,
311			type: 'audio/mp4',
312         config: {
313            credentials: 'include'
314         }
315		 },
316	  ],
317	};
318	
319	// BFSG/WCAG 2.1 AA Compliance: Enhanced player event handling with accessibility
320	player.on('loadedmetadata', event => {
321		if(pData.targetTime>-1) {
322			event.detail.plyr.currentTime=pData.targetTime;
323		}
324		// Announce track loaded to screen readers
325		announceToScreenReader('Track geladen: ' + pData.full_title);
326	});
327	
328	player.on('ready', event => {
329		player_state="ready";
330		$('#topPlayer').show();
331		event.detail.plyr.pData=pData;
332		$('#plyr_close').off('click keydown');
333		$('#plyr_close').on('click keydown', async (e)=> {
334			if (e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return;
335			if (e.type === 'keydown') e.preventDefault();
336			announceToScreenReader('Player wird beendet');
337			await unregisterPlayer(event.detail.plyr,true,true);
338			window.close();
339		});
340		if(pData.start_playing) {
341			event.detail.plyr.play().then( () => {
342				announceToScreenReader('Wiedergabe gestartet');
343			}, (e) => {
344				// Autoplay blocked (popup not user-activated): stay paused, no alarming message
345				if (e && e.name === 'NotAllowedError') {
346					console.info('Autoplay nicht erlaubt – Benutzer kann Play drücken.');
347					return;
348				}
349				console.info(e);
350				announceToScreenReader('Wiedergabe konnte nicht starten. Bitte überprüfen Sie die Browser-Einstellungen für automatische Wiedergabe.');
351			});
352		}
353		// Announce player ready to screen readers
354		// announceToScreenReader('Player bereit. ' + pData.full_title);
355	});
356	
357	player.on('playing', event => {	
358		if(window.opener) {
359			let pDataL=pData;
360			document.title="►"+" "+pDataL.full_title+" - HolyShop Hörspiel Player";
361		}
362	});
363	
364	player.on('pause', event => {	
365		if(window.opener) {
366			let pDataL=pData;
367			document.title="❚❚"+" "+pDataL.full_title+" - HolyShop Hörspiel Player";
368		}
369		// Announce pause state to screen readers
370		announceToScreenReader('Pausiert: ' + pData.full_title);
371	});
372	
373	// BFSG/WCAG 2.1 AA Compliance: Error handling with screen reader announcements
374	player.on('error', event => {
375		const plyrInstance = event.detail?.plyr ?? player;
376		const media = plyrInstance?.media ?? plyrInstance?.elements?.media;
377		const src = (media && media.currentSrc) ? media.currentSrc : '';
378		const errMsg = (media && media.error && media.error.message) ? media.error.message : '';
379		// Ignore Plyr internal blank.mp4 (decode often fails in audio-only context); harmless
380		if (/blank\.mp4/i.test(src) || /blank\.mp4/i.test(errMsg)) {
381			return;
382		}
383		// Ignore errors in first 3s (autoplay rejection, Plyr internal blank.mp4 from other element)
384		if (typeof initTime === 'number' && (Date.now() - initTime) < 3000) {
385			return;
386		}
387		if (player_state === 'ready') {
388			// When message is blank (e.g. privacy.resistFingerprinting or 403/5xx), show generic message
389			const displayMsg = (errMsg && errMsg.length > 0) ? errMsg : 'Netzwerk- oder Serverfehler. Bitte erneut versuchen.';
390			console.error('Player error:', displayMsg, event);
391			announceToScreenReader('Fehler beim Laden des Audio-Tracks. ' + (errMsg && errMsg.length > 0 ? '' : 'Bitte versuchen Sie es erneut.'));
392		} else {
393			console.info('Player error (before ready):', event.detail?.plyr?.media?.error?.message || event);
394		}
395	});
396	
397	// BFSG/WCAG 2.1 AA Compliance: Loading state announcements
398	player.on('loadstart', event => {
399		announceToScreenReader('Lade Audio-Track...');
400	});
401	
402	player.on('canplay', event => {
403		announceToScreenReader('Audio-Track bereit zur Wiedergabe');
404	});
405	
406	// BFSG/WCAG 2.1 AA Compliance: Volume change announcements
407	player.on('volumechange', event => {
408		const volume = Math.round(event.detail.plyr.volume * 100);
409		const muted = event.detail.plyr.muted;
410		if (muted) {
411			announceToScreenReader('Audio stummgeschaltet');
412		} else {
413			announceToScreenReader('Lautstärke: ' + volume + ' Prozent');
414		}
415	});
416	
417	window.addEventListener('unload', () => {
418		if(player_state=="destroyed") {
419			return;
420		}
421		// store Playback for resume after page change (guard: pData only set after 'ready')
422		if (!player || !player.pData) {
423			return;
424		}
425		let pData=player.pData;
426		if(player.currentTime) {
427			let curTime=player.currentTime || 0;
428			var cData={
429				pid:pData.prod_id,
430				prvw:pData.preview,
431				ct:player.currentTime,
432				pl:player.playing,
433				t:pData.title,
434				s:pData.serie,
435				e:pData.episode,
436				c:pData.cover,
437			};
438			writeCookieData('holyplayer',cData);
439			unregisterPlayer(player,false,false);
440		}
441		else {
442			console.error('could not save playback, player_state:'+player_state);
443		}
444		if(this) this.removeEventListener('unload',arguments.callee);
445	});
446}
447
448// BFSG/WCAG 2.1 AA Compliance: Screen reader announcement function
449function announceToScreenReader(message) {
450	// Create or get existing live region
451	let liveRegion = document.getElementById('player-status');
452	if (!liveRegion) {
453		liveRegion = document.createElement('div');
454		liveRegion.id = 'player-status';
455		liveRegion.className = 'sr-only';
456		liveRegion.setAttribute('aria-live', 'polite');
457		liveRegion.setAttribute('aria-atomic', 'true');
458		document.body.appendChild(liveRegion);
459	}
460	
461	// Update the live region with the message
462	liveRegion.textContent = message;
463	
464	// Clear the message after a delay to allow for multiple announcements
465	setTimeout(() => {
466		liveRegion.textContent = '';
467	}, 3000);
468}
469
470async function playStream(element,resumedata=false) {
471	if(element===false) {
472		var pData=createPData(element,resumedata);
473	}
474	else {
475		var pData=element;
476	}
477
478	await unregisterPlayer(player,false,true,true);
479	
480	// BFSG/WCAG 2.1 AA Compliance: Announce stream loading to screen readers
481	announceToScreenReader('Lade Hörspiel: ' + pData.full_title);
482	
483	urlexists(webstream_base+'webstream.php?0=0&1='+wurl+'&2='+pData.prod_id+'&3='+pData.preview, function (status) {
484		if (status === 200) {
485			// Execute code if successful
486			if(window.opener) {
487				document.title=pData.full_title;
488			}
489			$('#tp_serie').html(pData.serie);
490			$('#tp_episode').html(pData.episode+':');
491			$('#tp_title').html(pData.title);
492			$('#tp_cover').html('<img src="'+pData.cover+'" alt="Cover '+pData.full_title+'">');
493
494			// BFSG/WCAG 2.1 AA Compliance: Announce successful load to screen readers
495			announceToScreenReader('Hörspiel erfolgreich geladen: ' + pData.full_title);
496
497			initPlayer(pData);
498		}
499		else {
500			// BFSG/WCAG 2.1 AA Compliance: Announce error to screen readers
501			announceToScreenReader('Fehler beim Laden des Hörspiels. Bitte versuchen Sie es erneut.');
502			console.error('Stream loading failed with status:', status);
503			return;
504		}
505	});
506}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.