Php-Version-2

PHP ParaNerd 1 Views Size: 36.42 KB Posted on: Dec 10, 25 @ 9:09 PM
  1. 1<?php
  2. 2
  3. 3/**
  4. 4 * Plugin Name: Persistent Radio Player PWA
  5. 5 * Description: A persistent internet radio player for WordPress with PWA installation capability
  6. 6 * Version: 2.0.0
  7. 7 * Author: Your Name
  8. 8
  9. 9 */
  10. 10
  11. 11// Prevent direct access
  12. 12
  13. 13if (!defined('ABSPATH')) {
  14. 14
  15. 15 exit;
  16. 16
  17. 17}
  18. 18
  19. 19class PersistentRadioPlayerPWA {
  20. 20
  21. 21
  22. 22
  23. 23 private $config = [
  24. 24
  25. 25 'stream_url' => 'https://s76.radiolize.com:8050/radio.mp3',
  26. 26
  27. 27 'api_url' => 'https://s76.radiolize.com/api/nowplaying/18',
  28. 28
  29. 29 'station_name' => 'Paranormal FM',
  30. 30
  31. 31 'station_id' => 18,
  32. 32
  33. 33 'app_name' => 'Paranormal FM Radio',
  34. 34
  35. 35 'app_short_name' => 'Paranormal FM',
  36. 36
  37. 37 'app_description' => 'Listen to Paranormal FM - True Crime, Mystery & Supernatural Stories',
  38. 38
  39. 39 'theme_color' => '#000000',
  40. 40
  41. 41 'background_color' => '#000000',
  42. 42
  43. 43 'start_url' => '/'
  44. 44
  45. 45 ];
  46. 46
  47. 47
  48. 48
  49. 49 public function __construct() {
  50. 50
  51. 51 add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts']);
  52. 52
  53. 53 add_action('wp_footer', [$this, 'render_player']);
  54. 54
  55. 55 add_action('wp_head', [$this, 'add_pwa_meta_tags']);
  56. 56
  57. 57 add_action('wp_head', [$this, 'add_service_worker_support']);
  58. 58
  59. 59 add_action('init', [$this, 'handle_pwa_requests']);
  60. 60
  61. 61 add_action('init', [$this, 'add_rewrite_rules']);
  62. 62
  63. 63 add_action('admin_menu', [$this, 'add_admin_menu']);
  64. 64
  65. 65
  66. 66
  67. 67 // PWA specific hooks
  68. 68
  69. 69 add_action('wp_ajax_get_radio_metadata', [$this, 'get_radio_metadata']);
  70. 70
  71. 71 add_action('wp_ajax_nopriv_get_radio_metadata', [$this, 'get_radio_metadata']);
  72. 72
  73. 73
  74. 74
  75. 75 register_activation_hook(__FILE__, [$this, 'flush_rewrite_rules']);
  76. 76
  77. 77 register_deactivation_hook(__FILE__, [$this, 'flush_rewrite_rules']);
  78. 78
  79. 79 }
  80. 80
  81. 81
  82. 82
  83. 83 public function add_rewrite_rules() {
  84. 84
  85. 85 // Service Worker
  86. 86
  87. 87 add_rewrite_rule('^paranormal-fm-sw\.js$', 'index.php?paranormal_fm_sw=1', 'top');
  88. 88
  89. 89 add_rewrite_tag('%paranormal_fm_sw%', '([^&]+)');
  90. 90
  91. 91
  92. 92
  93. 93 // Web App Manifest
  94. 94
  95. 95 add_rewrite_rule('^paranormal-fm-manifest\.json$', 'index.php?paranormal_fm_manifest=1', 'top');
  96. 96
  97. 97 add_rewrite_tag('%paranormal_fm_manifest%', '([^&]+)');
  98. 98
  99. 99
  100. 100
  101. 101 // Offline page
  102. 102
  103. 103 add_rewrite_rule('^paranormal-fm-offline\.html$', 'index.php?paranormal_fm_offline=1', 'top');
  104. 104
  105. 105 add_rewrite_tag('%paranormal_fm_offline%', '([^&]+)');
  106. 106
  107. 107 }
  108. 108
  109. 109
  110. 110
  111. 111 public function flush_rewrite_rules() {
  112. 112
  113. 113 flush_rewrite_rules();
  114. 114
  115. 115 }
  116. 116
  117. 117
  118. 118
  119. 119 public function handle_pwa_requests() {
  120. 120
  121. 121 if (get_query_var('paranormal_fm_sw')) {
  122. 122
  123. 123 $this->serve_service_worker();
  124. 124
  125. 125 exit;
  126. 126
  127. 127 }
  128. 128
  129. 129
  130. 130
  131. 131 if (get_query_var('paranormal_fm_manifest')) {
  132. 132
  133. 133 $this->serve_manifest();
  134. 134
  135. 135 exit;
  136. 136
  137. 137 }
  138. 138
  139. 139
  140. 140
  141. 141 if (get_query_var('paranormal_fm_offline')) {
  142. 142
  143. 143 $this->serve_offline_page();
  144. 144
  145. 145 exit;
  146. 146
  147. 147 }
  148. 148
  149. 149 }
  150. 150
  151. 151
  152. 152
  153. 153 private function serve_service_worker() {
  154. 154
  155. 155 header('Content-Type: application/javascript');
  156. 156
  157. 157 header('Service-Worker-Allowed: /');
  158. 158
  159. 159 header('Cache-Control: no-cache, no-store, must-revalidate');
  160. 160
  161. 161 header('Pragma: no-cache');
  162. 162
  163. 163 header('Expires: 0');
  164. 164
  165. 165
  166. 166
  167. 167 echo $this->get_enhanced_service_worker_content();
  168. 168
  169. 169 }
  170. 170
  171. 171
  172. 172
  173. 173 private function serve_manifest() {
  174. 174
  175. 175 header('Content-Type: application/json');
  176. 176
  177. 177 header('Cache-Control: public, max-age=86400'); // Cache for 24 hours
  178. 178
  179. 179
  180. 180
  181. 181 $manifest = [
  182. 182
  183. 183 'name' => $this->config['app_name'],
  184. 184
  185. 185 'short_name' => $this->config['app_short_name'],
  186. 186
  187. 187 'description' => $this->config['app_description'],
  188. 188
  189. 189 'start_url' => $this->config['start_url'],
  190. 190
  191. 191 'display' => 'standalone',
  192. 192
  193. 193 'background_color' => $this->config['background_color'],
  194. 194
  195. 195 'theme_color' => $this->config['theme_color'],
  196. 196
  197. 197 'orientation' => 'portrait-primary',
  198. 198
  199. 199 'scope' => '/',
  200. 200
  201. 201 'icons' => [
  202. 202
  203. 203 [
  204. 204
  205. 205 'src' => plugin_dir_url(__FILE__) . 'assets/icons/icon-72x72.png',
  206. 206
  207. 207 'sizes' => '72x72',
  208. 208
  209. 209 'type' => 'image/png',
  210. 210
  211. 211 'purpose' => 'any'
  212. 212
  213. 213 ],
  214. 214
  215. 215 [
  216. 216
  217. 217 'src' => plugin_dir_url(__FILE__) . 'assets/icons/icon-96x96.png',
  218. 218
  219. 219 'sizes' => '96x96',
  220. 220
  221. 221 'type' => 'image/png',
  222. 222
  223. 223 'purpose' => 'any'
  224. 224
  225. 225 ],
  226. 226
  227. 227 [
  228. 228
  229. 229 'src' => plugin_dir_url(__FILE__) . 'assets/icons/icon-128x128.png',
  230. 230
  231. 231 'sizes' => '128x128',
  232. 232
  233. 233 'type' => 'image/png',
  234. 234
  235. 235 'purpose' => 'any'
  236. 236
  237. 237 ],
  238. 238
  239. 239 [
  240. 240
  241. 241 'src' => plugin_dir_url(__FILE__) . 'assets/icons/icon-144x144.png',
  242. 242
  243. 243 'sizes' => '144x144',
  244. 244
  245. 245 'type' => 'image/png',
  246. 246
  247. 247 'purpose' => 'any'
  248. 248
  249. 249 ],
  250. 250
  251. 251 [
  252. 252
  253. 253 'src' => plugin_dir_url(__FILE__) . 'assets/icons/icon-152x152.png',
  254. 254
  255. 255 'sizes' => '152x152',
  256. 256
  257. 257 'type' => 'image/png',
  258. 258
  259. 259 'purpose' => 'any'
  260. 260
  261. 261 ],
  262. 262
  263. 263 [
  264. 264
  265. 265 'src' => plugin_dir_url(__FILE__) . 'assets/icons/icon-192x192.png',
  266. 266
  267. 267 'sizes' => '192x192',
  268. 268
  269. 269 'type' => 'image/png',
  270. 270
  271. 271 'purpose' => 'any maskable'
  272. 272
  273. 273 ],
  274. 274
  275. 275 [
  276. 276
  277. 277 'src' => plugin_dir_url(__FILE__) . 'assets/icons/icon-384x384.png',
  278. 278
  279. 279 'sizes' => '384x384',
  280. 280
  281. 281 'type' => 'image/png',
  282. 282
  283. 283 'purpose' => 'any'
  284. 284
  285. 285 ],
  286. 286
  287. 287 [
  288. 288
  289. 289 'src' => plugin_dir_url(__FILE__) . 'assets/icons/icon-512x512.png',
  290. 290
  291. 291 'sizes' => '512x512',
  292. 292
  293. 293 'type' => 'image/png',
  294. 294
  295. 295 'purpose' => 'any maskable'
  296. 296
  297. 297 ]
  298. 298
  299. 299 ],
  300. 300
  301. 301 'categories' => ['music', 'entertainment', 'radio'],
  302. 302
  303. 303 'lang' => 'en',
  304. 304
  305. 305 'screenshots' => [
  306. 306
  307. 307 [
  308. 308
  309. 309 'src' => plugin_dir_url(__FILE__) . 'assets/screenshots/desktop.png',
  310. 310
  311. 311 'sizes' => '1280x720',
  312. 312
  313. 313 'type' => 'image/png',
  314. 314
  315. 315 'form_factor' => 'wide'
  316. 316
  317. 317 ],
  318. 318
  319. 319 [
  320. 320
  321. 321 'src' => plugin_dir_url(__FILE__) . 'assets/screenshots/mobile.png',
  322. 322
  323. 323 'sizes' => '375x667',
  324. 324
  325. 325 'type' => 'image/png',
  326. 326
  327. 327 'form_factor' => 'narrow'
  328. 328
  329. 329 ]
  330. 330
  331. 331 ],
  332. 332
  333. 333 'shortcuts' => [
  334. 334
  335. 335 [
  336. 336
  337. 337 'name' => 'Play Radio',
  338. 338
  339. 339 'short_name' => 'Play',
  340. 340
  341. 341 'description' => 'Start playing Paranormal FM',
  342. 342
  343. 343 'url' => '/?action=play',
  344. 344
  345. 345 'icons' => [
  346. 346
  347. 347 [
  348. 348
  349. 349 'src' => plugin_dir_url(__FILE__) . 'assets/icons/play-96x96.png',
  350. 350
  351. 351 'sizes' => '96x96'
  352. 352
  353. 353 ]
  354. 354
  355. 355 ]
  356. 356
  357. 357 ]
  358. 358
  359. 359 ]
  360. 360
  361. 361 ];
  362. 362
  363. 363
  364. 364
  365. 365 echo json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
  366. 366
  367. 367 }
  368. 368
  369. 369
  370. 370
  371. 371 private function serve_offline_page() {
  372. 372
  373. 373 header('Content-Type: text/html; charset=UTF-8');
  374. 374
  375. 375 header('Cache-Control: public, max-age=86400');
  376. 376
  377. 377
  378. 378
  379. 379 echo $this->get_offline_page_content();
  380. 380
  381. 381 }
  382. 382
  383. 383
  384. 384
  385. 385 private function get_offline_page_content() {
  386. 386
  387. 387 return '<!DOCTYPE html>
  388. 388
  389. 389<html lang="en">
  390. 390
  391. 391<head>
  392. 392
  393. 393 <meta charset="UTF-8">
  394. 394
  395. 395 <meta name="viewport" content="width=device-width, initial-scale=1.0">
  396. 396
  397. 397 <title>Offline - Paranormal FM</title>
  398. 398
  399. 399 <style>
  400. 400
  401. 401 body {
  402. 402
  403. 403 font-family: "Poppins", sans-serif;
  404. 404
  405. 405 background: linear-gradient(135deg, #000000 0%, #1a1a1a 100%);
  406. 406
  407. 407 color: #ffffff;
  408. 408
  409. 409 margin: 0;
  410. 410
  411. 411 padding: 0;
  412. 412
  413. 413 height: 100vh;
  414. 414
  415. 415 display: flex;
  416. 416
  417. 417 flex-direction: column;
  418. 418
  419. 419 align-items: center;
  420. 420
  421. 421 justify-content: center;
  422. 422
  423. 423 text-align: center;
  424. 424
  425. 425 }
  426. 426
  427. 427 .offline-container {
  428. 428
  429. 429 max-width: 400px;
  430. 430
  431. 431 padding: 2rem;
  432. 432
  433. 433 }
  434. 434
  435. 435 .offline-icon {
  436. 436
  437. 437 font-size: 4rem;
  438. 438
  439. 439 margin-bottom: 1rem;
  440. 440
  441. 441 opacity: 0.7;
  442. 442
  443. 443 }
  444. 444
  445. 445 .offline-title {
  446. 446
  447. 447 font-size: 1.5rem;
  448. 448
  449. 449 font-weight: 600;
  450. 450
  451. 451 margin-bottom: 1rem;
  452. 452
  453. 453 }
  454. 454
  455. 455 .offline-message {
  456. 456
  457. 457 font-size: 1rem;
  458. 458
  459. 459 opacity: 0.8;
  460. 460
  461. 461 line-height: 1.6;
  462. 462
  463. 463 margin-bottom: 2rem;
  464. 464
  465. 465 }
  466. 466
  467. 467 .retry-button {
  468. 468
  469. 469 background: #ffffff;
  470. 470
  471. 471 color: #000000;
  472. 472
  473. 473 border: none;
  474. 474
  475. 475 padding: 12px 24px;
  476. 476
  477. 477 border-radius: 25px;
  478. 478
  479. 479 font-weight: 600;
  480. 480
  481. 481 cursor: pointer;
  482. 482
  483. 483 transition: all 0.3s ease;
  484. 484
  485. 485 }
  486. 486
  487. 487 .retry-button:hover {
  488. 488
  489. 489 background: #f0f0f0;
  490. 490
  491. 491 transform: translateY(-2px);
  492. 492
  493. 493 }
  494. 494
  495. 495 </style>
  496. 496
  497. 497</head>
  498. 498
  499. 499<body>
  500. 500
  501. 501 <div class="offline-container">
  502. 502
  503. 503 <div class="offline-icon">📻</div>
  504. 504
  505. 505 <h1 class="offline-title">You\'re Offline</h1>
  506. 506
  507. 507 <p class="offline-message">
  508. 508
  509. 509 It looks like you\'re not connected to the internet.
  510. 510
  511. 511 The radio stream requires an internet connection to play.
  512. 512
  513. 513 </p>
  514. 514
  515. 515 <button class="retry-button" onclick="window.location.reload()">
  516. 516
  517. 517 Try Again
  518. 518
  519. 519 </button>
  520. 520
  521. 521 </div>
  522. 522
  523. 523
  524. 524
  525. 525 <script>
  526. 526
  527. 527 // Check connection status and auto-retry
  528. 528
  529. 529 window.addEventListener("online", () => {
  530. 530
  531. 531 window.location.reload();
  532. 532
  533. 533 });
  534. 534
  535. 535 </script>
  536. 536
  537. 537</body>
  538. 538
  539. 539</html>';
  540. 540
  541. 541 }
  542. 542
  543. 543
  544. 544
  545. 545 private function get_enhanced_service_worker_content() {
  546. 546
  547. 547 return '
  548. 548
  549. 549// Enhanced Service Worker for Paranormal FM PWA
  550. 550
  551. 551const CACHE_NAME = "paranormal-fm-v2.0.0";
  552. 552
  553. 553const OFFLINE_URL = "/paranormal-fm-offline.html";
  554. 554
  555. 555// Cache resources
  556. 556
  557. 557const CACHE_URLS = [
  558. 558
  559. 559 "/",
  560. 560
  561. 561 "/paranormal-fm-offline.html",
  562. 562
  563. 563 "' . plugin_dir_url(__FILE__) . 'assets/player.js",
  564. 564
  565. 565 "' . plugin_dir_url(__FILE__) . 'assets/player.css",
  566. 566
  567. 567 "' . plugin_dir_url(__FILE__) . 'assets/icons/icon-192x192.png",
  568. 568
  569. 569 "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"
  570. 570
  571. 571];
  572. 572
  573. 573// Audio state management
  574. 574
  575. 575let audioState = {
  576. 576
  577. 577 isPlaying: false,
  578. 578
  579. 579 volume: 1,
  580. 580
  581. 581 streamUrl: "' . $this->config['stream_url'] . '",
  582. 582
  583. 583 lastUpdate: Date.now(),
  584. 584
  585. 585 metadata: null
  586. 586
  587. 587};
  588. 588
  589. 589// Install event
  590. 590
  591. 591self.addEventListener("install", (event) => {
  592. 592
  593. 593 console.log("Paranormal FM PWA Service Worker installing...");
  594. 594
  595. 595
  596. 596
  597. 597 event.waitUntil(
  598. 598
  599. 599 caches.open(CACHE_NAME).then((cache) => {
  600. 600
  601. 601 console.log("Caching app shell");
  602. 602
  603. 603 return cache.addAll(CACHE_URLS);
  604. 604
  605. 605 }).then(() => {
  606. 606
  607. 607 return self.skipWaiting();
  608. 608
  609. 609 })
  610. 610
  611. 611 );
  612. 612
  613. 613});
  614. 614
  615. 615// Activate event
  616. 616
  617. 617self.addEventListener("activate", (event) => {
  618. 618
  619. 619 console.log("Paranormal FM PWA Service Worker activated");
  620. 620
  621. 621
  622. 622
  623. 623 event.waitUntil(
  624. 624
  625. 625 caches.keys().then((cacheNames) => {
  626. 626
  627. 627 return Promise.all(
  628. 628
  629. 629 cacheNames.map((cacheName) => {
  630. 630
  631. 631 if (cacheName !== CACHE_NAME) {
  632. 632
  633. 633 console.log("Deleting old cache:", cacheName);
  634. 634
  635. 635 return caches.delete(cacheName);
  636. 636
  637. 637 }
  638. 638
  639. 639 })
  640. 640
  641. 641 );
  642. 642
  643. 643 }).then(() => {
  644. 644
  645. 645 return self.clients.claim();
  646. 646
  647. 647 })
  648. 648
  649. 649 );
  650. 650
  651. 651});
  652. 652
  653. 653// Fetch event with network-first strategy for API calls
  654. 654
  655. 655self.addEventListener("fetch", (event) => {
  656. 656
  657. 657 const url = new URL(event.request.url);
  658. 658
  659. 659
  660. 660
  661. 661 // Handle API requests with network-first strategy
  662. 662
  663. 663 if (url.pathname.includes("/api/") || url.hostname.includes("radiolize.com")) {
  664. 664
  665. 665 event.respondWith(
  666. 666
  667. 667 fetch(event.request)
  668. 668
  669. 669 .then((response) => {
  670. 670
  671. 671 // Clone and cache successful API responses
  672. 672
  673. 673 if (response.ok) {
  674. 674
  675. 675 const responseClone = response.clone();
  676. 676
  677. 677 caches.open(CACHE_NAME).then((cache) => {
  678. 678
  679. 679 cache.put(event.request, responseClone);
  680. 680
  681. 681 });
  682. 682
  683. 683 }
  684. 684
  685. 685 return response;
  686. 686
  687. 687 })
  688. 688
  689. 689 .catch(() => {
  690. 690
  691. 691 // Fallback to cache for API requests
  692. 692
  693. 693 return caches.match(event.request);
  694. 694
  695. 695 })
  696. 696
  697. 697 );
  698. 698
  699. 699 return;
  700. 700
  701. 701 }
  702. 702
  703. 703
  704. 704
  705. 705 // Handle navigation requests
  706. 706
  707. 707 if (event.request.mode === "navigate") {
  708. 708
  709. 709 event.respondWith(
  710. 710
  711. 711 fetch(event.request)
  712. 712
  713. 713 .catch(() => {
  714. 714
  715. 715 return caches.match(OFFLINE_URL);
  716. 716
  717. 717 })
  718. 718
  719. 719 );
  720. 720
  721. 721 return;
  722. 722
  723. 723 }
  724. 724
  725. 725
  726. 726
  727. 727 // Handle other requests with cache-first strategy
  728. 728
  729. 729 event.respondWith(
  730. 730
  731. 731 caches.match(event.request)
  732. 732
  733. 733 .then((response) => {
  734. 734
  735. 735 if (response) {
  736. 736
  737. 737 return response;
  738. 738
  739. 739 }
  740. 740
  741. 741
  742. 742
  743. 743 return fetch(event.request).then((response) => {
  744. 744
  745. 745 // Cache successful responses
  746. 746
  747. 747 if (response.status === 200) {
  748. 748
  749. 749 const responseClone = response.clone();
  750. 750
  751. 751 caches.open(CACHE_NAME).then((cache) => {
  752. 752
  753. 753 cache.put(event.request, responseClone);
  754. 754
  755. 755 });
  756. 756
  757. 757 }
  758. 758
  759. 759 return response;
  760. 760
  761. 761 });
  762. 762
  763. 763 })
  764. 764
  765. 765 );
  766. 766
  767. 767});
  768. 768
  769. 769// Message handling for audio state and PWA functionality
  770. 770
  771. 771self.addEventListener("message", (event) => {
  772. 772
  773. 773 const { type, data } = event.data;
  774. 774
  775. 775
  776. 776
  777. 777 switch (type) {
  778. 778
  779. 779 case "AUDIO_STATE_UPDATE":
  780. 780
  781. 781 audioState = { ...audioState, ...data, lastUpdate: Date.now() };
  782. 782
  783. 783 broadcastToClients("AUDIO_STATE_SYNC", audioState);
  784. 784
  785. 785 break;
  786. 786
  787. 787
  788. 788
  789. 789 case "GET_AUDIO_STATE":
  790. 790
  791. 791 event.ports[0].postMessage({
  792. 792
  793. 793 type: "AUDIO_STATE_RESPONSE",
  794. 794
  795. 795 data: audioState
  796. 796
  797. 797 });
  798. 798
  799. 799 break;
  800. 800
  801. 801
  802. 802
  803. 803 case "SKIP_WAITING":
  804. 804
  805. 805 self.skipWaiting();
  806. 806
  807. 807 break;
  808. 808
  809. 809
  810. 810
  811. 811 case "CLAIM_CLIENTS":
  812. 812
  813. 813 self.clients.claim();
  814. 814
  815. 815 break;
  816. 816
  817. 817
  818. 818
  819. 819 case "GET_VERSION":
  820. 820
  821. 821 event.ports[0].postMessage({
  822. 822
  823. 823 type: "VERSION_RESPONSE",
  824. 824
  825. 825 data: { version: CACHE_NAME }
  826. 826
  827. 827 });
  828. 828
  829. 829 break;
  830. 830
  831. 831 }
  832. 832
  833. 833});
  834. 834
  835. 835// Background sync for metadata when online
  836. 836
  837. 837self.addEventListener("sync", (event) => {
  838. 838
  839. 839 if (event.tag === "metadata-sync") {
  840. 840
  841. 841 event.waitUntil(syncMetadata());
  842. 842
  843. 843 }
  844. 844
  845. 845});
  846. 846
  847. 847async function syncMetadata() {
  848. 848
  849. 849 try {
  850. 850
  851. 851 const response = await fetch("' . $this->config['api_url'] . '");
  852. 852
  853. 853 const data = await response.json();
  854. 854
  855. 855
  856. 856
  857. 857 audioState.metadata = data;
  858. 858
  859. 859 broadcastToClients("METADATA_SYNC", data);
  860. 860
  861. 861 console.log("Background metadata sync completed");
  862. 862
  863. 863 } catch (error) {
  864. 864
  865. 865 console.error("Background metadata sync failed:", error);
  866. 866
  867. 867 }
  868. 868
  869. 869}
  870. 870
  871. 871function broadcastToClients(type, data) {
  872. 872
  873. 873 self.clients.matchAll().then(clients => {
  874. 874
  875. 875 clients.forEach(client => {
  876. 876
  877. 877 client.postMessage({ type, data });
  878. 878
  879. 879 });
  880. 880
  881. 881 });
  882. 882
  883. 883}
  884. 884
  885. 885// Push notification handling (for future features)
  886. 886
  887. 887self.addEventListener("push", (event) => {
  888. 888
  889. 889 if (event.data) {
  890. 890
  891. 891 const data = event.data.json();
  892. 892
  893. 893 const options = {
  894. 894
  895. 895 body: data.body || "New content available",
  896. 896
  897. 897 icon: "/paranormal-fm-manifest.json",
  898. 898
  899. 899 badge: "' . plugin_dir_url(__FILE__) . 'assets/icons/badge-72x72.png",
  900. 900
  901. 901 vibrate: [200, 100, 200],
  902. 902
  903. 903 data: data.data || {},
  904. 904
  905. 905 actions: [
  906. 906
  907. 907 {
  908. 908
  909. 909 action: "open",
  910. 910
  911. 911 title: "Open App"
  912. 912
  913. 913 }
  914. 914
  915. 915 ]
  916. 916
  917. 917 };
  918. 918
  919. 919
  920. 920
  921. 921 event.waitUntil(
  922. 922
  923. 923 self.registration.showNotification(data.title || "Paranormal FM", options)
  924. 924
  925. 925 );
  926. 926
  927. 927 }
  928. 928
  929. 929});
  930. 930
  931. 931// Notification click handling
  932. 932
  933. 933self.addEventListener("notificationclick", (event) => {
  934. 934
  935. 935 event.notification.close();
  936. 936
  937. 937
  938. 938
  939. 939 if (event.action === "open" || !event.action) {
  940. 940
  941. 941 event.waitUntil(
  942. 942
  943. 943 clients.openWindow("/")
  944. 944
  945. 945 );
  946. 946
  947. 947 }
  948. 948
  949. 949});
  950. 950
  951. 951';
  952. 952
  953. 953 }
  954. 954
  955. 955
  956. 956
  957. 957 public function add_pwa_meta_tags() {
  958. 958
  959. 959 echo '
  960. 960
  961. 961<!-- PWA Meta Tags -->
  962. 962
  963. 963<meta name="theme-color" content="' . esc_attr($this->config['theme_color']) . '">
  964. 964
  965. 965<meta name="application-name" content="' . esc_attr($this->config['app_name']) . '">
  966. 966
  967. 967<meta name="apple-mobile-web-app-capable" content="yes">
  968. 968
  969. 969<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
  970. 970
  971. 971<meta name="apple-mobile-web-app-title" content="' . esc_attr($this->config['app_short_name']) . '">
  972. 972
  973. 973<meta name="msapplication-TileColor" content="' . esc_attr($this->config['theme_color']) . '">
  974. 974
  975. 975<meta name="msapplication-TileImage" content="' . plugin_dir_url(__FILE__) . 'assets/icons/icon-144x144.png">
  976. 976
  977. 977<!-- Web App Manifest -->
  978. 978
  979. 979<link rel="manifest" href="/paranormal-fm-manifest.json">
  980. 980
  981. 981<!-- Apple Touch Icons -->
  982. 982
  983. 983<link rel="apple-touch-icon" href="' . plugin_dir_url(__FILE__) . 'assets/icons/icon-180x180.png">
  984. 984
  985. 985<link rel="apple-touch-icon" sizes="152x152" href="' . plugin_dir_url(__FILE__) . 'assets/icons/icon-152x152.png">
  986. 986
  987. 987<link rel="apple-touch-icon" sizes="144x144" href="' . plugin_dir_url(__FILE__) . 'assets/icons/icon-144x144.png">
  988. 988
  989. 989<!-- Favicon -->
  990. 990
  991. 991<link rel="icon" type="image/png" sizes="32x32" href="' . plugin_dir_url(__FILE__) . 'assets/icons/favicon-32x32.png">
  992. 992
  993. 993<link rel="icon" type="image/png" sizes="16x16" href="' . plugin_dir_url(__FILE__) . 'assets/icons/favicon-16x16.png">
  994. 994
  995. 995';
  996. 996
  997. 997 }
  998. 998
  999. 999
  1000. 1000
  1001. 1001 public function add_service_worker_support() {
  1002. 1002
  1003. 1003 $sw_url = home_url('/paranormal-fm-sw.js');
  1004. 1004
  1005. 1005 echo "<script>
  1006. 1006
  1007. 1007 // PWA Service Worker Registration
  1008. 1008
  1009. 1009 if ('serviceWorker' in navigator) {
  1010. 1010
  1011. 1011 window.addEventListener('load', () => {
  1012. 1012
  1013. 1013 navigator.serviceWorker.register('{$sw_url}', { scope: '/' })
  1014. 1014
  1015. 1015 .then((registration) => {
  1016. 1016
  1017. 1017 console.log('Paranormal FM PWA Service Worker registered:', registration);
  1018. 1018
  1019. 1019
  1020. 1020
  1021. 1021 // Check for updates
  1022. 1022
  1023. 1023 registration.addEventListener('updatefound', () => {
  1024. 1024
  1025. 1025 const newWorker = registration.installing;
  1026. 1026
  1027. 1027 newWorker.addEventListener('statechange', () => {
  1028. 1028
  1029. 1029 if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
  1030. 1030
  1031. 1031 // Show update available notification
  1032. 1032
  1033. 1033 if (window.radioPlayer) {
  1034. 1034
  1035. 1035 window.radioPlayer.showUpdateAvailable();
  1036. 1036
  1037. 1037 }
  1038. 1038
  1039. 1039 }
  1040. 1040
  1041. 1041 });
  1042. 1042
  1043. 1043 });
  1044. 1044
  1045. 1045 })
  1046. 1046
  1047. 1047 .catch((error) => {
  1048. 1048
  1049. 1049 console.log('Service Worker registration failed:', error);
  1050. 1050
  1051. 1051 });
  1052. 1052
  1053. 1053 });
  1054. 1054
  1055. 1055 }
  1056. 1056
  1057. 1057
  1058. 1058
  1059. 1059 // PWA Install Prompt
  1060. 1060
  1061. 1061 let deferredPrompt;
  1062. 1062
  1063. 1063 window.addEventListener('beforeinstallprompt', (e) => {
  1064. 1064
  1065. 1065 console.log('PWA install prompt triggered');
  1066. 1066
  1067. 1067 e.preventDefault();
  1068. 1068
  1069. 1069 deferredPrompt = e;
  1070. 1070
  1071. 1071
  1072. 1072
  1073. 1073 // Show custom install button
  1074. 1074
  1075. 1075 if (window.radioPlayer) {
  1076. 1076
  1077. 1077 window.radioPlayer.showInstallPrompt();
  1078. 1078
  1079. 1079 }
  1080. 1080
  1081. 1081 });
  1082. 1082
  1083. 1083
  1084. 1084
  1085. 1085 window.addEventListener('appinstalled', (evt) => {
  1086. 1086
  1087. 1087 console.log('PWA was installed');
  1088. 1088
  1089. 1089 if (window.radioPlayer) {
  1090. 1090
  1091. 1091 window.radioPlayer.hideInstallPrompt();
  1092. 1092
  1093. 1093 }
  1094. 1094
  1095. 1095 });
  1096. 1096
  1097. 1097 </script>";
  1098. 1098
  1099. 1099 }
  1100. 1100
  1101. 1101
  1102. 1102
  1103. 1103 public function enqueue_scripts() {
  1104. 1104
  1105. 1105 if (is_admin()) return;
  1106. 1106
  1107. 1107
  1108. 1108
  1109. 1109 wp_enqueue_script(
  1110. 1110
  1111. 1111 'persistent-radio-player-pwa',
  1112. 1112
  1113. 1113 plugin_dir_url(__FILE__) . 'assets/player-pwa.js',
  1114. 1114
  1115. 1115 [],
  1116. 1116
  1117. 1117 '2.0.0',
  1118. 1118
  1119. 1119 true
  1120. 1120
  1121. 1121 );
  1122. 1122
  1123. 1123
  1124. 1124
  1125. 1125 wp_enqueue_style(
  1126. 1126
  1127. 1127 'persistent-radio-player-pwa',
  1128. 1128
  1129. 1129 plugin_dir_url(__FILE__) . 'assets/player-pwa.css',
  1130. 1130
  1131. 1131 [],
  1132. 1132
  1133. 1133 '2.0.0'
  1134. 1134
  1135. 1135 );
  1136. 1136
  1137. 1137
  1138. 1138
  1139. 1139 wp_enqueue_style(
  1140. 1140
  1141. 1141 'font-awesome',
  1142. 1142
  1143. 1143 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css'
  1144. 1144
  1145. 1145 );
  1146. 1146
  1147. 1147
  1148. 1148
  1149. 1149 wp_localize_script('persistent-radio-player-pwa', 'radioPlayerConfig', [
  1150. 1150
  1151. 1151 'streamUrl' => $this->config['stream_url'],
  1152. 1152
  1153. 1153 'apiUrl' => admin_url('admin-ajax.php'),
  1154. 1154
  1155. 1155 'directApiUrl' => $this->config['api_url'],
  1156. 1156
  1157. 1157 'stationName' => $this->config['station_name'],
  1158. 1158
  1159. 1159 'stationId' => $this->config['station_id'],
  1160. 1160
  1161. 1161 'serviceWorkerUrl' => home_url('/paranormal-fm-sw.js'),
  1162. 1162
  1163. 1163 'manifestUrl' => home_url('/paranormal-fm-manifest.json'),
  1164. 1164
  1165. 1165 'offlineUrl' => home_url('/paranormal-fm-offline.html'),
  1166. 1166
  1167. 1167 'appName' => $this->config['app_name'],
  1168. 1168
  1169. 1169 'nonce' => wp_create_nonce('radio_player_nonce'),
  1170. 1170
  1171. 1171 'debug' => WP_DEBUG,
  1172. 1172
  1173. 1173 'isPWA' => true
  1174. 1174
  1175. 1175 ]);
  1176. 1176
  1177. 1177 }
  1178. 1178
  1179. 1179
  1180. 1180
  1181. 1181 public function render_player() {
  1182. 1182
  1183. 1183 if (is_admin()) return;
  1184. 1184
  1185. 1185 echo $this->get_player_html();
  1186. 1186
  1187. 1187 }
  1188. 1188
  1189. 1189
  1190. 1190
  1191. 1191 private function get_player_html() {
  1192. 1192
  1193. 1193 return '
  1194. 1194
  1195. 1195 <div id="radio-player" class="radio-player footer">
  1196. 1196
  1197. 1197 <div class="player-content">
  1198. 1198
  1199. 1199 <div class="player-left">
  1200. 1200
  1201. 1201 <div class="controls-container">
  1202. 1202
  1203. 1203 <div class="controls">
  1204. 1204
  1205. 1205 <button class="control-button play-button" title="Play">
  1206. 1206
  1207. 1207 <i class="fas fa-play"></i>
  1208. 1208
  1209. 1209 </button>
  1210. 1210
  1211. 1211 <button class="control-button volume-button" title="Volume">
  1212. 1212
  1213. 1213 <i class="fas fa-volume-up"></i>
  1214. 1214
  1215. 1215 </button>
  1216. 1216
  1217. 1217 <div class="volume-bar-container">
  1218. 1218
  1219. 1219 <input type="range" class="volume-slider" min="0" max="100" value="100">
  1220. 1220
  1221. 1221 </div>
  1222. 1222
  1223. 1223 <button class="control-button history-button" title="Song History">
  1224. 1224
  1225. 1225 <i class="fas fa-history"></i>
  1226. 1226
  1227. 1227 </button>
  1228. 1228
  1229. 1229 <button class="control-button install-button" title="Install App" style="display: none;">
  1230. 1230
  1231. 1231 <i class="fas fa-download"></i>
  1232. 1232
  1233. 1233 </button>
  1234. 1234
  1235. 1235 </div>
  1236. 1236
  1237. 1237 </div>
  1238. 1238
  1239. 1239 </div>
  1240. 1240
  1241. 1241
  1242. 1242
  1243. 1243 <div class="player-center">
  1244. 1244
  1245. 1245 <div class="album-artwork">
  1246. 1246
  1247. 1247 <img class="artwork-image" src="" alt="Album Art" style="display: none;" />
  1248. 1248
  1249. 1249 <div class="artwork-placeholder">♪</div>
  1250. 1250
  1251. 1251 </div>
  1252. 1252
  1253. 1253 <div class="player-info">
  1254. 1254
  1255. 1255 <div class="song-info">
  1256. 1256
  1257. 1257 <div class="song-name">' . esc_html($this->config['station_name']) . '</div>
  1258. 1258
  1259. 1259 <div class="artist-name">Click play to start streaming</div>
  1260. 1260
  1261. 1261 </div>
  1262. 1262
  1263. 1263 </div>
  1264. 1264
  1265. 1265 </div>
  1266. 1266
  1267. 1267
  1268. 1268
  1269. 1269 <div class="player-right">
  1270. 1270
  1271. 1271 <div class="time-info">
  1272. 1272
  1273. 1273 <span class="elapsed-time">0:00</span>
  1274. 1274
  1275. 1275 <span>/</span>
  1276. 1276
  1277. 1277 <span class="duration-time">0:00</span>
  1278. 1278
  1279. 1279 </div>
  1280. 1280
  1281. 1281 <div class="radio-name">' . esc_html($this->config['station_name']) . '</div>
  1282. 1282
  1283. 1283 <div class="live-container">
  1284. 1284
  1285. 1285 <div class="live-indicator"></div>
  1286. 1286
  1287. 1287 <div class="live-text">LIVE</div>
  1288. 1288
  1289. 1289 </div>
  1290. 1290
  1291. 1291 </div>
  1292. 1292
  1293. 1293 </div>
  1294. 1294
  1295. 1295
  1296. 1296
  1297. 1297 <div class="time-bar-container">
  1298. 1298
  1299. 1299 <div class="time-bar">
  1300. 1300
  1301. 1301 <div class="time-bar-progress"></div>
  1302. 1302
  1303. 1303 </div>
  1304. 1304
  1305. 1305 </div>
  1306. 1306
  1307. 1307
  1308. 1308
  1309. 1309 <!-- PWA Install Banner -->
  1310. 1310
  1311. 1311 <div class="install-banner" style="display: none;">
  1312. 1312
  1313. 1313 <div class="install-banner-content">
  1314. 1314
  1315. 1315 <div class="install-banner-icon">📱</div>
  1316. 1316
  1317. 1317 <div class="install-banner-text">
  1318. 1318
  1319. 1319 <div class="install-banner-title">Install Paranormal FM</div>
  1320. 1320
  1321. 1321 <div class="install-banner-description">Add to your home screen for quick access</div>
  1322. 1322
  1323. 1323 </div>
  1324. 1324
  1325. 1325 <div class="install-banner-actions">
  1326. 1326
  1327. 1327 <button class="install-banner-button install-now">Install</button>
  1328. 1328
  1329. 1329 <button class="install-banner-button install-later">Later</button>
  1330. 1330
  1331. 1331 </div>
  1332. 1332
  1333. 1333 </div>
  1334. 1334
  1335. 1335 </div>
  1336. 1336
  1337. 1337
  1338. 1338
  1339. 1339 <!-- Update Available Banner -->
  1340. 1340
  1341. 1341 <div class="update-banner" style="display: none;">
  1342. 1342
  1343. 1343 <div class="update-banner-content">
  1344. 1344
  1345. 1345 <div class="update-banner-text">
  1346. 1346
  1347. 1347 <div class="update-banner-title">Update Available</div>
  1348. 1348
  1349. 1349 <div class="update-banner-description">A new version is ready</div>
  1350. 1350
  1351. 1351 </div>
  1352. 1352
  1353. 1353 <button class="update-banner-button">Update</button>
  1354. 1354
  1355. 1355 </div>
  1356. 1356
  1357. 1357 </div>
  1358. 1358
  1359. 1359
  1360. 1360
  1361. 1361 <div class="history-modal" style="display: none;">
  1362. 1362
  1363. 1363 <div class="history-modal-overlay"></div>
  1364. 1364
  1365. 1365 <div class="history-modal-content">
  1366. 1366
  1367. 1367 <h3 class="history-modal-title">Song History</h3>
  1368. 1368
  1369. 1369 <button class="history-modal-close">
  1370. 1370
  1371. 1371 <i class="fas fa-times"></i>
  1372. 1372
  1373. 1373 </button>
  1374. 1374
  1375. 1375 <div class="history-list"></div>
  1376. 1376
  1377. 1377 </div>
  1378. 1378
  1379. 1379 </div>
  1380. 1380
  1381. 1381 </div>';
  1382. 1382
  1383. 1383 }
  1384. 1384
  1385. 1385
  1386. 1386
  1387. 1387 public function get_radio_metadata() {
  1388. 1388
  1389. 1389 if (!wp_verify_nonce($_POST['nonce'] ?? '', 'radio_player_nonce')) {
  1390. 1390
  1391. 1391 wp_send_json_error(['message' => 'Security check failed']);
  1392. 1392
  1393. 1393 return;
  1394. 1394
  1395. 1395 }
  1396. 1396
  1397. 1397
  1398. 1398
  1399. 1399 $response = wp_remote_get($this->config['api_url'], [
  1400. 1400
  1401. 1401 'timeout' => 10,
  1402. 1402
  1403. 1403 'sslverify' => false,
  1404. 1404
  1405. 1405 'headers' => [
  1406. 1406
  1407. 1407 'User-Agent' => 'WordPress Radio Player PWA/2.0.0',
  1408. 1408
  1409. 1409 'Accept' => 'application/json'
  1410. 1410
  1411. 1411 ]
  1412. 1412
  1413. 1413 ]);
  1414. 1414
  1415. 1415
  1416. 1416
  1417. 1417 if (is_wp_error($response)) {
  1418. 1418
  1419. 1419 wp_send_json_error([
  1420. 1420
  1421. 1421 'message' => 'Failed to fetch metadata',
  1422. 1422
  1423. 1423 'error' => $response->get_error_message()
  1424. 1424
  1425. 1425 ]);
  1426. 1426
  1427. 1427 return;
  1428. 1428
  1429. 1429 }
  1430. 1430
  1431. 1431
  1432. 1432
  1433. 1433 $body = wp_remote_retrieve_body($response);
  1434. 1434
  1435. 1435 $data = json_decode($body, true);
  1436. 1436
  1437. 1437
  1438. 1438
  1439. 1439 if (json_last_error() !== JSON_ERROR_NONE) {
  1440. 1440
  1441. 1441 wp_send_json_error(['message' => 'Invalid JSON response']);
  1442. 1442
  1443. 1443 return;
  1444. 1444
  1445. 1445 }
  1446. 1446
  1447. 1447
  1448. 1448
  1449. 1449 $processed_data = $this->process_api_data($data);
  1450. 1450
  1451. 1451 wp_send_json_success($processed_data);
  1452. 1452
  1453. 1453 }
  1454. 1454
  1455. 1455
  1456. 1456
  1457. 1457 private function process_api_data($data) {
  1458. 1458
  1459. 1459 $processed = [
  1460. 1460
  1461. 1461 'station' => $data['station'] ?? null,
  1462. 1462
  1463. 1463 'listeners' => $data['listeners'] ?? null,
  1464. 1464
  1465. 1465 'live' => $data['live'] ?? null,
  1466. 1466
  1467. 1467 'is_online' => $data['is_online'] ?? true
  1468. 1468
  1469. 1469 ];
  1470. 1470
  1471. 1471
  1472. 1472
  1473. 1473 if (isset($data['now_playing'])) {
  1474. 1474
  1475. 1475 $now_playing = $data['now_playing'];
  1476. 1476
  1477. 1477 if (is_string($now_playing)) {
  1478. 1478
  1479. 1479 $now_playing = json_decode($now_playing, true);
  1480. 1480
  1481. 1481 }
  1482. 1482
  1483. 1483 $processed['now_playing'] = $now_playing;
  1484. 1484
  1485. 1485 }
  1486. 1486
  1487. 1487
  1488. 1488
  1489. 1489 if (isset($data['song_history'])) {
  1490. 1490
  1491. 1491 $song_history = $data['song_history'];
  1492. 1492
  1493. 1493 if (is_string($song_history)) {
  1494. 1494
  1495. 1495 $song_history = json_decode($song_history, true);
  1496. 1496
  1497. 1497 }
  1498. 1498
  1499. 1499 $processed['song_history'] = $song_history;
  1500. 1500
  1501. 1501 }
  1502. 1502
  1503. 1503
  1504. 1504
  1505. 1505 return $processed;
  1506. 1506
  1507. 1507 }
  1508. 1508
  1509. 1509
  1510. 1510
  1511. 1511 public function add_admin_menu() {
  1512. 1512
  1513. 1513 add_options_page(
  1514. 1514
  1515. 1515 'Radio Player PWA Settings',
  1516. 1516
  1517. 1517 'Radio Player PWA',
  1518. 1518
  1519. 1519 'manage_options',
  1520. 1520
  1521. 1521 'persistent-radio-player-pwa',
  1522. 1522
  1523. 1523 [$this, 'admin_page']
  1524. 1524
  1525. 1525 );
  1526. 1526
  1527. 1527 }
  1528. 1528
  1529. 1529
  1530. 1530
  1531. 1531 public function admin_page() {
  1532. 1532
  1533. 1533 ?>
  1534. 1534
  1535. 1535 <div class="wrap">
  1536. 1536
  1537. 1537 <h1>Radio Player PWA Settings</h1>
  1538. 1538
  1539. 1539
  1540. 1540
  1541. 1541 <div class="notice notice-info">
  1542. 1542
  1543. 1543 <p><strong>PWA Features Active!</strong> Your radio player now supports Progressive Web App installation.</p>
  1544. 1544
  1545. 1545 </div>
  1546. 1546
  1547. 1547
  1548. 1548
  1549. 1549 <h2>Current Configuration</h2>
  1550. 1550
  1551. 1551 <table class="form-table">
  1552. 1552
  1553. 1553 <tr>
  1554. 1554
  1555. 1555 <th>App Name:</th>
  1556. 1556
  1557. 1557 <td><?php echo esc_html($this->config['app_name']); ?></td>
  1558. 1558
  1559. 1559 </tr>
  1560. 1560
  1561. 1561 <tr>
  1562. 1562
  1563. 1563 <th>Stream URL:</th>
  1564. 1564
  1565. 1565 <td><?php echo esc_html($this->config['stream_url']); ?></td>
  1566. 1566
  1567. 1567 </tr>
  1568. 1568
  1569. 1569 <tr>
  1570. 1570
  1571. 1571 <th>API URL:</th>
  1572. 1572
  1573. 1573 <td><?php echo esc_html($this->config['api_url']); ?></td>
  1574. 1574
  1575. 1575 </tr>
  1576. 1576
  1577. 1577 <tr>
  1578. 1578
  1579. 1579 <th>Manifest URL:</th>
  1580. 1580
  1581. 1581 <td><a href="<?php echo home_url('/paranormal-fm-manifest.json'); ?>" target="_blank"><?php echo home_url('/paranormal-fm-manifest.json'); ?></a></td>
  1582. 1582
  1583. 1583 </tr>
  1584. 1584
  1585. 1585 <tr>
  1586. 1586
  1587. 1587 <th>Service Worker URL:</th>
  1588. 1588
  1589. 1589 <td><a href="<?php echo home_url('/paranormal-fm-sw.js'); ?>" target="_blank"><?php echo home_url('/paranormal-fm-sw.js'); ?></a></td>
  1590. 1590
  1591. 1591 </tr>
  1592. 1592
  1593. 1593 </table>
  1594. 1594
  1595. 1595
  1596. 1596
  1597. 1597 <h2>PWA Installation Instructions</h2>
  1598. 1598
  1599. 1599 <div class="card">
  1600. 1600
  1601. 1601 <h3>For Users:</h3>
  1602. 1602
  1603. 1603 <ul>
  1604. 1604
  1605. 1605 <li><strong>Chrome/Edge:</strong> Look for the install icon in the address bar or use the "Install Paranormal FM" button</li>
  1606. 1606
  1607. 1607 <li><strong>Firefox:</strong> Use the "Add to Home Screen" option in the menu</li>
  1608. 1608
  1609. 1609 <li><strong>Safari (iOS):</strong> Use "Add to Home Screen" from the share menu</li>
  1610. 1610
  1611. 1611 <li><strong>Android:</strong> Look for the "Add to Home Screen" prompt or banner</li>
  1612. 1612
  1613. 1613 </ul>
  1614. 1614
  1615. 1615 </div>
  1616. 1616
  1617. 1617
  1618. 1618
  1619. 1619 <h2>Test PWA Features</h2>
  1620. 1620
  1621. 1621 <button id="test-pwa" class="button button-primary">Test PWA Installation</button>
  1622. 1622
  1623. 1623 <button id="test-sw" class="button">Test Service Worker</button>
  1624. 1624
  1625. 1625 <button id="test-manifest" class="button">Test Manifest</button>
  1626. 1626
  1627. 1627 <div id="test-results" style="margin-top: 15px;"></div>
  1628. 1628
  1629. 1629
  1630. 1630
  1631. 1631 <script>
  1632. 1632
  1633. 1633 document.getElementById('test-pwa').addEventListener('click', function() {
  1634. 1634
  1635. 1635 const results = document.getElementById('test-results');
  1636. 1636
  1637. 1637 results.innerHTML = '<p>Testing PWA features...</p>';
  1638. 1638
  1639. 1639
  1640. 1640
  1641. 1641 let checks = [];
  1642. 1642
  1643. 1643
  1644. 1644
  1645. 1645 // Check HTTPS
  1646. 1646
  1647. 1647 if (location.protocol === 'https:') {
  1648. 1648
  1649. 1649 checks.push('✓ HTTPS: Required for PWA');
  1650. 1650
  1651. 1651 } else {
  1652. 1652
  1653. 1653 checks.push('✗ HTTPS: Required for PWA (currently HTTP)');
  1654. 1654
  1655. 1655 }
  1656. 1656
  1657. 1657
  1658. 1658
  1659. 1659 // Check Service Worker
  1660. 1660
  1661. 1661 if ('serviceWorker' in navigator) {
  1662. 1662
  1663. 1663 checks.push('✓ Service Worker: Supported');
  1664. 1664
  1665. 1665 navigator.serviceWorker.getRegistrations().then(registrations => {
  1666. 1666
  1667. 1667 if (registrations.length > 0) {
  1668. 1668
  1669. 1669 checks.push('✓ Service Worker: Registered');
  1670. 1670
  1671. 1671 } else {
  1672. 1672
  1673. 1673 checks.push('âš  Service Worker: Not yet registered');
  1674. 1674
  1675. 1675 }
  1676. 1676
  1677. 1677 updateResults();
  1678. 1678
  1679. 1679 });
  1680. 1680
  1681. 1681 } else {
  1682. 1682
  1683. 1683 checks.push('✗ Service Worker: Not supported');
  1684. 1684
  1685. 1685 }
  1686. 1686
  1687. 1687
  1688. 1688
  1689. 1689 // Check Web App Manifest
  1690. 1690
  1691. 1691 const manifest = document.querySelector('link[rel="manifest"]');
  1692. 1692
  1693. 1693 if (manifest) {
  1694. 1694
  1695. 1695 checks.push('✓ Web App Manifest: Found');
  1696. 1696
  1697. 1697 } else {
  1698. 1698
  1699. 1699 checks.push('✗ Web App Manifest: Not found');
  1700. 1700
  1701. 1701 }
  1702. 1702
  1703. 1703
  1704. 1704
  1705. 1705 function updateResults() {
  1706. 1706
  1707. 1707 results.innerHTML = '<h4>PWA Readiness Check:</h4><ul><li>' + checks.join('</li><li>') + '</li></ul>';
  1708. 1708
  1709. 1709 }
  1710. 1710
  1711. 1711
  1712. 1712
  1713. 1713 setTimeout(updateResults, 1000);
  1714. 1714
  1715. 1715 });
  1716. 1716
  1717. 1717
  1718. 1718
  1719. 1719 document.getElementById('test-sw').addEventListener('click', function() {
  1720. 1720
  1721. 1721 fetch('<?php echo home_url('/paranormal-fm-sw.js'); ?>')
  1722. 1722
  1723. 1723 .then(response => {
  1724. 1724
  1725. 1725 document.getElementById('test-results').innerHTML = response.ok ?
  1726. 1726
  1727. 1727 '<p style="color: green;">✓ Service Worker accessible</p>' :
  1728. 1728
  1729. 1729 '<p style="color: red;">✗ Service Worker not accessible</p>';
  1730. 1730
  1731. 1731 })
  1732. 1732
  1733. 1733 .catch(() => {
  1734. 1734
  1735. 1735 document.getElementById('test-results').innerHTML = '<p style="color: red;">✗ Service Worker request failed</p>';
  1736. 1736
  1737. 1737 });
  1738. 1738
  1739. 1739 });
  1740. 1740
  1741. 1741
  1742. 1742
  1743. 1743 document.getElementById('test-manifest').addEventListener('click', function() {
  1744. 1744
  1745. 1745 fetch('<?php echo home_url('/paranormal-fm-manifest.json'); ?>')
  1746. 1746
  1747. 1747 .then(response => response.json())
  1748. 1748
  1749. 1749 .then(data => {
  1750. 1750
  1751. 1751 document.getElementById('test-results').innerHTML =
  1752. 1752
  1753. 1753 '<p style="color: green;">✓ Manifest accessible</p>' +
  1754. 1754
  1755. 1755 '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
  1756. 1756
  1757. 1757 })
  1758. 1758
  1759. 1759 .catch(() => {
  1760. 1760
  1761. 1761 document.getElementById('test-results').innerHTML = '<p style="color: red;">✗ Manifest request failed</p>';
  1762. 1762
  1763. 1763 });
  1764. 1764
  1765. 1765 });
  1766. 1766
  1767. 1767 </script>
  1768. 1768
  1769. 1769 </div>
  1770. 1770
  1771. 1771 <?php
  1772. 1772
  1773. 1773 }
  1774. 1774
  1775. 1775}
  1776. 1776
  1777. 1777new PersistentRadioPlayerPWA();
  1778. 1778
  1779. 1779?>

Raw Paste

Comments 0
Login to post a comment.
  • No comments yet. Be the first.
Login to post a comment. Login or Register
We use cookies. To comply with GDPR in the EU and the UK we have to show you these.

We use cookies and similar technologies to keep this website functional (including spam protection via Google reCAPTCHA or Cloudflare Turnstile), and — with your consent — to measure usage and show ads. See Privacy.