Play video on hover

Facebook, Vimeo, Youtube, Streamable, Tiktok, Instagram, Twitter, X, Dailymotion, Coub, Spotify, SoundCloud, Apple Podcasts, Amazon Music, Deezer, Tidal, Ted, Pbs, Odysee, Playeur, Bitchute, Rss - play on hover

  1. // ==UserScript==
  2. // @name Play video on hover
  3. // @namespace https://lukaszmical.pl/
  4. // @version 0.6.0
  5. // @description Facebook, Vimeo, Youtube, Streamable, Tiktok, Instagram, Twitter, X, Dailymotion, Coub, Spotify, SoundCloud, Apple Podcasts, Amazon Music, Deezer, Tidal, Ted, Pbs, Odysee, Playeur, Bitchute, Rss - play on hover
  6. // @author Łukasz Micał
  7. // @match *://*/*
  8. // @icon https://static-00.iconduck.com/assets.00/cursor-hover-icon-512x439-vou7bdac.png
  9. // ==/UserScript==
  10.  
  11. // libs/share/src/ui/SvgComponent.ts
  12. const SvgComponent = class {
  13. constructor(tag, props = {}) {
  14. this.element = Dom.createSvg({ tag, ...props });
  15. }
  16.  
  17. addClassName(...className) {
  18. this.element.classList.add(...className);
  19. }
  20.  
  21. event(event, callback) {
  22. this.element.addEventListener(event, callback);
  23. }
  24.  
  25. getElement() {
  26. return this.element;
  27. }
  28.  
  29. mount(parent) {
  30. parent.appendChild(this.element);
  31. }
  32. };
  33.  
  34. // libs/share/src/ui/Dom.ts
  35. var Dom = class _Dom {
  36. static appendChildren(element, children, isSvgMode = false) {
  37. if (children) {
  38. element.append(
  39. ..._Dom.array(children).map((item) => {
  40. if (typeof item === 'string') {
  41. return document.createTextNode(item);
  42. }
  43. if (item instanceof HTMLElement || item instanceof SVGElement) {
  44. return item;
  45. }
  46. if (item instanceof Component || item instanceof SvgComponent) {
  47. return item.getElement();
  48. }
  49. const isSvg =
  50. 'svg' === item.tag
  51. ? true
  52. : 'foreignObject' === item.tag
  53. ? false
  54. : isSvgMode;
  55. if (isSvg) {
  56. return _Dom.createSvg(item);
  57. }
  58. return _Dom.create(item);
  59. })
  60. );
  61. }
  62. }
  63.  
  64. static applyAttrs(element, attrs) {
  65. if (attrs) {
  66. Object.entries(attrs).forEach(([key, value]) => {
  67. if (value === void 0 || value === false) {
  68. element.removeAttribute(key);
  69. } else {
  70. element.setAttribute(key, `${value}`);
  71. }
  72. });
  73. }
  74. }
  75.  
  76. static applyClass(element, classes) {
  77. if (classes) {
  78. element.classList.add(...classes.split(' ').filter(Boolean));
  79. }
  80. }
  81.  
  82. static applyEvents(element, events) {
  83. if (events) {
  84. Object.entries(events).forEach(([name, callback]) => {
  85. element.addEventListener(name, callback);
  86. });
  87. }
  88. }
  89.  
  90. static applyStyles(element, styles) {
  91. if (styles) {
  92. Object.entries(styles).forEach(([key, value]) => {
  93. const name = key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
  94. element.style.setProperty(name, value);
  95. });
  96. }
  97. }
  98.  
  99. static array(element) {
  100. return Array.isArray(element) ? element : [element];
  101. }
  102.  
  103. static create(data) {
  104. const element = document.createElement(data.tag);
  105. _Dom.appendChildren(element, data.children);
  106. _Dom.applyClass(element, data.classes);
  107. _Dom.applyAttrs(element, data.attrs);
  108. _Dom.applyEvents(element, data.events);
  109. _Dom.applyStyles(element, data.styles);
  110. return element;
  111. }
  112.  
  113. static createSvg(data) {
  114. const element = document.createElementNS(
  115. 'http://www.w3.org/2000/svg',
  116. data.tag
  117. );
  118. _Dom.appendChildren(element, data.children, true);
  119. _Dom.applyClass(element, data.classes);
  120. _Dom.applyAttrs(element, data.attrs);
  121. _Dom.applyEvents(element, data.events);
  122. _Dom.applyStyles(element, data.styles);
  123. return element;
  124. }
  125.  
  126. static element(tag, classes, children) {
  127. return _Dom.create({ tag, children, classes });
  128. }
  129.  
  130. static elementSvg(tag, classes, children) {
  131. return _Dom.createSvg({ tag, children, classes });
  132. }
  133. };
  134.  
  135. // libs/share/src/ui/Component.ts
  136. var Component = class {
  137. constructor(tag, props = {}) {
  138. this.element = Dom.create({ tag, ...props });
  139. }
  140.  
  141. addClassName(...className) {
  142. this.element.classList.add(...className);
  143. }
  144.  
  145. event(event, callback) {
  146. this.element.addEventListener(event, callback);
  147. }
  148.  
  149. getElement() {
  150. return this.element;
  151. }
  152.  
  153. mount(parent) {
  154. parent.appendChild(this.element);
  155. }
  156. };
  157.  
  158. // apps/on-hover-preview/src/components/PreviewPopup.ts
  159. const PreviewPopup = class _PreviewPopup extends Component {
  160. constructor() {
  161. super('div', {
  162. attrs: {
  163. id: _PreviewPopup.ID,
  164. },
  165. children: {
  166. tag: 'iframe',
  167. attrs: {
  168. allow:
  169. 'autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share',
  170. allowFullscreen: true,
  171. },
  172. styles: {
  173. width: '100%',
  174. border: 'none',
  175. height: '100%',
  176. },
  177. },
  178. styles: {
  179. width: '500px',
  180. background: '#444',
  181. boxShadow: 'rgb(218, 218, 218) 1px 1px 5px',
  182. display: 'none',
  183. height: '300px',
  184. overflow: 'hidden',
  185. position: 'absolute',
  186. zIndex: '9999',
  187. },
  188. });
  189. this.iframeActive = false;
  190. this.iframe = this.element.children[0];
  191. if (!document.querySelector(`#${_PreviewPopup.ID}`)) {
  192. this.mount(document.body);
  193. document.addEventListener('click', this.hidePopup.bind(this));
  194. }
  195. }
  196.  
  197. static {
  198. this.ID = 'play-on-hover-popup';
  199. }
  200.  
  201. hidePopup() {
  202. this.iframeActive = false;
  203. this.iframe.src = '';
  204. this.element.style.display = 'none';
  205. }
  206.  
  207. showPopup(e, url, service) {
  208. if (!this.iframeActive) {
  209. this.iframe.src = url;
  210. this.iframeActive = true;
  211. Dom.applyStyles(this.element, {
  212. display: 'block',
  213. left: `${e.pageX}px`,
  214. top: `${e.pageY}px`,
  215. ...service.styles,
  216. });
  217. }
  218. }
  219. };
  220.  
  221. // libs/share/src/ui/Events.ts
  222. const Events = class {
  223. static intendHover(validate, mouseover, mouseleave, timeout = 500) {
  224. let hover = false;
  225. let id = 0;
  226. const onHover = (event) => {
  227. if (!event.target || !validate(event.target)) {
  228. return;
  229. }
  230. const element = event.target;
  231. hover = true;
  232. element.addEventListener(
  233. 'mouseleave',
  234. (ev) => {
  235. mouseleave?.call(element, ev);
  236. clearTimeout(id);
  237. hover = false;
  238. },
  239. { once: true }
  240. );
  241. clearTimeout(id);
  242. id = window.setTimeout(() => {
  243. if (hover) {
  244. mouseover.call(element, event);
  245. }
  246. }, timeout);
  247. };
  248. document.body.addEventListener('mouseover', onHover);
  249. }
  250. };
  251.  
  252. // apps/on-hover-preview/src/helpers/LinkHover.ts
  253. const LinkHover = class {
  254. constructor(services, onHover) {
  255. this.services = services;
  256. this.onHover = onHover;
  257. Events.intendHover(
  258. this.isValidLink.bind(this),
  259. this.onAnchorHover.bind(this)
  260. );
  261. }
  262.  
  263. anchorElement(node) {
  264. if (!(node instanceof HTMLElement)) {
  265. return void 0;
  266. }
  267. if (node instanceof HTMLAnchorElement) {
  268. return node;
  269. }
  270. const parent = node.closest('a');
  271. if (parent instanceof HTMLElement) {
  272. return parent;
  273. }
  274. return void 0;
  275. }
  276.  
  277. findService(url = '') {
  278. return this.services.find((service) => service.isValidUrl(url));
  279. }
  280.  
  281. isValidLink(node) {
  282. const anchor = this.anchorElement(node);
  283. if (!anchor || !anchor.href || anchor.href === '#') {
  284. return false;
  285. }
  286. return true;
  287. }
  288.  
  289. async onAnchorHover(ev) {
  290. const anchor = this.anchorElement(ev.target);
  291. if (!anchor) {
  292. return;
  293. }
  294. const service = this.findService(anchor.href);
  295. if (!service) {
  296. return;
  297. }
  298. const previewUrl = await service.embeddedVideoUrl(anchor);
  299. if (!previewUrl) {
  300. return;
  301. }
  302. this.onHover(ev, previewUrl, service);
  303. }
  304. };
  305.  
  306. // apps/on-hover-preview/src/services/base/BaseService.ts
  307. const defaultServiceStyle = {
  308. width: '500px',
  309. height: '282px',
  310. };
  311. const BaseService = class {
  312. createUrl(url, params) {
  313. if (params) {
  314. return `${url}?${this.params(params)}`;
  315. }
  316. return url;
  317. }
  318.  
  319. extractId(url, match) {
  320. const result = this.match(url, match);
  321. return result?.id || '';
  322. }
  323.  
  324. isDarkmode() {
  325. return window.matchMedia('(prefers-color-scheme: dark)').matches;
  326. }
  327.  
  328. match(url, match) {
  329. const result = url.match(match);
  330. if (result && result.groups) {
  331. return result.groups;
  332. }
  333. return void 0;
  334. }
  335.  
  336. params(params) {
  337. return Object.entries(params)
  338. .map(([key, value]) => `${key}=${value}`)
  339. .join('&');
  340. }
  341.  
  342. theme(light, dark) {
  343. return window.matchMedia('(prefers-color-scheme: dark)').matches
  344. ? dark
  345. : light;
  346. }
  347. };
  348.  
  349. // apps/on-hover-preview/src/services/base/ServiceFactory.ts
  350. const ServiceFactory = class extends BaseService {
  351. constructor(config, styles = defaultServiceStyle) {
  352. super();
  353. this.config = config;
  354. this.styles = styles;
  355. this.initialStyles = styles;
  356. }
  357.  
  358. bindParams(url, params) {
  359. return Object.entries(params).reduce(
  360. (acc, [key, value]) =>
  361. acc.replace(`:${key}`, value !== void 0 ? `${value}` : ''),
  362. url
  363. );
  364. }
  365.  
  366. async embeddedVideoUrl(element) {
  367. const isDarkMode = this.isDarkmode();
  368. const patternParams = this.match(element.href, this.config.pattern) || {};
  369. const urlParams = {
  370. ...patternParams,
  371. ...this.urlParams(element),
  372. theme: isDarkMode ? 'dark' : 'light',
  373. };
  374. this.styles = {
  375. ...this.initialStyles,
  376. height: this.getHeight(urlParams),
  377. };
  378. const embedUrl = this.bindParams(
  379. this.createUrl(this.config.embedUrl, this.config.queryParams),
  380. urlParams
  381. );
  382. if (this.config.urlFunction) {
  383. return this.config.urlFunction({
  384. ...urlParams,
  385. url: embedUrl,
  386. });
  387. }
  388. return embedUrl;
  389. }
  390.  
  391. isValidUrl(url) {
  392. return this.config.pattern.test(url);
  393. }
  394.  
  395. getHeight(urlParams) {
  396. if (this.config.heightFunction) {
  397. return this.config.heightFunction(urlParams);
  398. }
  399. if (this.config.typeHeight && urlParams.type in this.config.typeHeight) {
  400. return this.config.typeHeight[urlParams.type];
  401. }
  402. return this.initialStyles.height;
  403. }
  404.  
  405. urlParams(element) {
  406. return {
  407. href: element.href,
  408. pathname: element.pathname,
  409. search: element.search,
  410. };
  411. }
  412. };
  413.  
  414. // apps/on-hover-preview/src/services/AmazonMusic.ts
  415. const AmazonMusic = class extends ServiceFactory {
  416. constructor() {
  417. super(
  418. {
  419. embedUrl: 'https://music.amazon.com/embed/:id',
  420. pattern:
  421. /music\.amazon\.com\/(?<type>albums|tracks|artists|playlists)\/(?<id>[^/?]+)/,
  422. typeHeight: { tracks: '250px' },
  423. },
  424. {
  425. width: '500px',
  426. borderRadius: '12px',
  427. height: '372px',
  428. }
  429. );
  430. }
  431. };
  432.  
  433. // apps/on-hover-preview/src/services/AppleMusic.ts
  434. const AppleMusic = class extends ServiceFactory {
  435. constructor() {
  436. super(
  437. {
  438. embedUrl: 'https://embed.:service.apple.com:pathname',
  439. pattern:
  440. /(?<service>music|podcasts)\.apple\.com\/.{2}\/(?<type>song|music-video|artist|album|podcast)/,
  441. typeHeight: {
  442. 'music-video': '281px',
  443. song: '175px',
  444. },
  445. },
  446. {
  447. width: '500px',
  448. borderRadius: '12px',
  449. height: '450px',
  450. }
  451. );
  452. }
  453. };
  454.  
  455. // apps/on-hover-preview/src/services/Bitchute.ts
  456. const Bitchute = class extends ServiceFactory {
  457. constructor() {
  458. super({
  459. embedUrl: 'https://bitchute.com/embed/:id',
  460. pattern: /bitchute\.com\/video\/(?<id>[^/?]+)\/?/,
  461. });
  462. }
  463. };
  464.  
  465. // apps/on-hover-preview/src/services/Coub.ts
  466. const Coub = class extends ServiceFactory {
  467. constructor() {
  468. super({
  469. embedUrl: 'https://coub.com/embed/:id',
  470. pattern: /coub\.com\/view\/(?<id>[^/]+)\/?/,
  471. queryParams: {
  472. autostart: 'true',
  473. muted: 'false',
  474. originalSize: 'false',
  475. startWithHD: 'true',
  476. },
  477. });
  478. }
  479. };
  480.  
  481. // apps/on-hover-preview/src/services/Dailymotion.ts
  482. const Dailymotion = class extends ServiceFactory {
  483. constructor() {
  484. super({
  485. embedUrl: 'https://geo.dailymotion.com/player.html?video=:id',
  486. pattern: /dailymotion\.com\/video\/(?<id>[^/?]+)/,
  487. });
  488. }
  489. };
  490.  
  491. // apps/on-hover-preview/src/services/Deezer.ts
  492. const Deezer = class extends ServiceFactory {
  493. constructor() {
  494. super(
  495. {
  496. embedUrl: 'https://widget.deezer.com/widget/:theme/:type/:id',
  497. pattern:
  498. /deezer\.com\/.{2}\/(?<type>album|playlist|track|artist|show|episode)\/(?<id>\d+)/,
  499. queryParams: {
  500. autoplay: 'true',
  501. radius: 'true',
  502. tracklist: 'false',
  503. },
  504. },
  505. {
  506. width: '500px',
  507. borderRadius: '10px',
  508. height: '300px',
  509. }
  510. );
  511. }
  512. };
  513.  
  514. // apps/on-hover-preview/src/services/Facebook.ts
  515. const Facebook = class extends ServiceFactory {
  516. constructor() {
  517. super({
  518. embedUrl: 'https://www.facebook.com/plugins/video.php',
  519. pattern: /https:\/\/(www\.|m\.)?facebook\.com\/[\w\-_]+\/videos\//,
  520. queryParams: {
  521. width: '500',
  522. autoplay: 'true',
  523. href: ':href',
  524. show_text: 'false',
  525. },
  526. });
  527. }
  528. };
  529.  
  530. // apps/on-hover-preview/src/services/Instagram.ts
  531. const Instagram = class extends ServiceFactory {
  532. constructor() {
  533. super(
  534. {
  535. embedUrl: 'https://www.instagram.com/p/:id/embed/',
  536. pattern: /instagram\.com\/(.+\/)?reel\/(?<id>[^/?]+)/,
  537. },
  538. {
  539. width: '300px',
  540. height: '500px',
  541. }
  542. );
  543. }
  544. };
  545.  
  546. // apps/on-hover-preview/src/services/Odysee.ts
  547. const Odysee = class extends ServiceFactory {
  548. constructor() {
  549. super({
  550. embedUrl: 'https://odysee.com/$/embed:pathname',
  551. pattern: /odysee\.com\/@/,
  552. queryParams: {
  553. autoplay: 'true',
  554. },
  555. });
  556. }
  557. };
  558.  
  559. // apps/on-hover-preview/src/services/Pbs.ts
  560. const Pbs = class extends ServiceFactory {
  561. constructor() {
  562. super({
  563. embedUrl: 'https://player.pbs.org/portalplayer/:id',
  564. pattern: /pbs\.org\/video\/(?<id>.+)?/,
  565. });
  566. }
  567. };
  568.  
  569. // apps/on-hover-preview/src/services/Playeur.ts
  570. const Playeur = class extends ServiceFactory {
  571. constructor() {
  572. super({
  573. embedUrl: 'https://playeur.com/embed/:id',
  574. pattern: /playeur\.com\/(v|embed)\/(?<id>[^/]+)\/?/,
  575. });
  576. }
  577. };
  578.  
  579. // apps/on-hover-preview/src/services/Podbean.ts
  580. const Podbean = class extends ServiceFactory {
  581. constructor() {
  582. super(
  583. {
  584. embedUrl: 'https://www.podbean.com/player-v2',
  585. pattern: /podbean\.com\/.+\/(?<type>dir|pb)-(?<id>[^/?]+)\/?/,
  586. queryParams: {
  587. i: ':id-:type',
  588. },
  589. },
  590. {
  591. width: '500px',
  592. height: '150px',
  593. }
  594. );
  595. }
  596. };
  597.  
  598. // apps/on-hover-preview/src/services/Rss.ts
  599. const Rss = class extends ServiceFactory {
  600. constructor() {
  601. super(
  602. {
  603. embedUrl: 'https://player.rss.com/:show/:id',
  604. heightFunction: ({ id }) => (id ? '152px' : '320px'),
  605. pattern: /rss\.com\/podcasts\/(?<show>[^/]+)\/(?<id>\d*)/,
  606. queryParams: {
  607. theme: ':theme',
  608. },
  609. },
  610. {
  611. width: '500px',
  612. borderRadius: '8px',
  613. height: '152px',
  614. }
  615. );
  616. }
  617. };
  618.  
  619. // apps/on-hover-preview/src/services/SoundCloud.ts
  620. const SoundCloud = class extends ServiceFactory {
  621. constructor() {
  622. super(
  623. {
  624. embedUrl: 'https://w.soundcloud.com/player',
  625. pattern: /soundcloud\.com\/[^/]+\/[^/?]+/,
  626. queryParams: {
  627. hide_related: 'true',
  628. auto_play: 'true',
  629. show_artwork: 'true',
  630. show_comments: 'false',
  631. show_teaser: 'false',
  632. url: ':href',
  633. visual: 'false',
  634. },
  635. },
  636. {
  637. width: '600px',
  638. height: '166px',
  639. }
  640. );
  641. }
  642. };
  643.  
  644. // apps/on-hover-preview/src/services/Spotify.ts
  645. const Spotify = class extends ServiceFactory {
  646. constructor() {
  647. super(
  648. {
  649. embedUrl: 'https://open.spotify.com/embed/:type/:id',
  650. pattern:
  651. /spotify\.com\/(.+\/)?(?<type>track|album|playlist|episode|artist|show)\/(?<id>[\w-]+)/,
  652. typeHeight: { track: '152px' },
  653. urlFunction: ({ type, url }) =>
  654. ['episode', 'show'].includes(type) ? `${url}/video` : url,
  655. },
  656. {
  657. width: '600px',
  658. borderRadius: '12px',
  659. height: '352px',
  660. }
  661. );
  662. }
  663. };
  664.  
  665. // apps/on-hover-preview/src/services/Streamable.ts
  666. const Streamable = class extends ServiceFactory {
  667. constructor() {
  668. super({
  669. embedUrl: 'https://streamable.com/o/:id',
  670. pattern: /streamable\.com\/([s|o]\/)?(?<id>[^?/]+).*$/,
  671. queryParams: {
  672. autoplay: '1',
  673. },
  674. });
  675. }
  676. };
  677.  
  678. // apps/on-hover-preview/src/services/Ted.ts
  679. const Ted = class extends ServiceFactory {
  680. constructor() {
  681. super({
  682. embedUrl: 'https://embed.ted.com/talks/:id',
  683. pattern: /ted\.com\/talks\/(?<id>[^/]+)\/?/,
  684. });
  685. }
  686. };
  687.  
  688. // apps/on-hover-preview/src/services/Tidal.ts
  689. const Tidal = class extends ServiceFactory {
  690. constructor() {
  691. super(
  692. {
  693. embedUrl: 'https://embed.tidal.com/:types/:id',
  694. pattern:
  695. /tidal\.com\/(.+\/)?(?<type>track|album|video|playlist)\/(?<id>\d+|[\w-]+)/,
  696. typeHeight: {
  697. video: '281px',
  698. playlist: '400px',
  699. track: '120px',
  700. },
  701. },
  702. {
  703. width: '500px',
  704. borderRadius: '10px',
  705. height: '300px',
  706. }
  707. );
  708. }
  709. };
  710.  
  711. // apps/on-hover-preview/src/services/Tiktok.ts
  712. const Tiktok = class extends ServiceFactory {
  713. constructor() {
  714. super(
  715. {
  716. embedUrl: 'https://www.tiktok.com/player/v1/:id',
  717. pattern: /tiktok\.com\/.+\/video\/(?<id>\d+)/,
  718. queryParams: {
  719. autoplay: 1,
  720. rel: 0,
  721. },
  722. },
  723. {
  724. width: '338px',
  725. height: '575px',
  726. }
  727. );
  728. }
  729. };
  730.  
  731. // apps/on-hover-preview/src/services/Twitter.ts
  732. const Twitter = class extends ServiceFactory {
  733. constructor() {
  734. super(
  735. {
  736. embedUrl: 'https://platform.:platform.com/embed/Tweet.html',
  737. pattern: /(?<platform>twitter|x)\.com\/.+\/status\/(?<id>\d+)\/video/,
  738. queryParams: {
  739. id: ':id',
  740. maxWidth: 480,
  741. width: 480,
  742. theme: ':theme',
  743. },
  744. },
  745. {
  746. width: '500px',
  747. height: '300px',
  748. }
  749. );
  750. }
  751. };
  752.  
  753. // apps/on-hover-preview/src/services/Vimeo.ts
  754. const Vimeo = class extends ServiceFactory {
  755. constructor() {
  756. super({
  757. embedUrl: 'https://player.vimeo.com/video/:id',
  758. pattern: /vimeo\.com(.+)*\/(?<id>\d+)\/?$/,
  759. });
  760. }
  761. };
  762.  
  763. // apps/on-hover-preview/src/services/Youtube.ts
  764. const YoutubeHelper = class {
  765. static getId(search) {
  766. return new URLSearchParams(search).get('v') || '';
  767. }
  768.  
  769. static getStartTime(search) {
  770. const start = new URLSearchParams(search).get('t') || '0s';
  771. const result = start.match(/(?:(?<h>\d+)h)?(?:(?<m>\d+)m)?(?<s>\d+)s/);
  772. if (result && result.groups) {
  773. return (
  774. Number(result.groups.h || '0') * 3600 +
  775. Number(result.groups.m || '0') * 60 +
  776. Number(result.groups.s || '0')
  777. );
  778. }
  779. return 0;
  780. }
  781. };
  782. const Youtube = class extends ServiceFactory {
  783. constructor() {
  784. super({
  785. embedUrl: 'https://www.youtube.com/embed/:id',
  786. pattern: /youtube\.com\/watch/,
  787. queryParams: {
  788. autoplay: 1,
  789. start: ':start',
  790. },
  791. urlFunction: ({ search, url }) =>
  792. this.bindParams(url, {
  793. id: YoutubeHelper.getId(search),
  794. start: YoutubeHelper.getStartTime(search),
  795. }),
  796. });
  797. }
  798. };
  799. const YoutubeShortcut = class extends ServiceFactory {
  800. constructor() {
  801. super({
  802. embedUrl: 'https://www.youtube.com/embed/:id',
  803. pattern: /youtu\.be\/(?<id>[^?/]+)/,
  804. queryParams: {
  805. autoplay: 1,
  806. start: ':start',
  807. },
  808. urlFunction: ({ search, url }) =>
  809. this.bindParams(url, {
  810. start: YoutubeHelper.getStartTime(search),
  811. }),
  812. });
  813. }
  814. };
  815. const YoutubeShorts = class extends ServiceFactory {
  816. constructor() {
  817. super(
  818. {
  819. embedUrl: 'https://www.youtube.com/embed/:id',
  820. pattern: /youtube\.com\/shorts\/(?<id>[^?/]+).*$/,
  821. queryParams: {
  822. autoplay: 1,
  823. },
  824. },
  825. {
  826. width: '256px',
  827. height: '454px',
  828. }
  829. );
  830. }
  831. };
  832.  
  833. // apps/on-hover-preview/src/main.ts
  834. function run() {
  835. const services = [
  836. Youtube,
  837. YoutubeShortcut,
  838. YoutubeShorts,
  839. Vimeo,
  840. Streamable,
  841. Facebook,
  842. Tiktok,
  843. Instagram,
  844. Twitter,
  845. Dailymotion,
  846. Dailymotion,
  847. Coub,
  848. Spotify,
  849. SoundCloud,
  850. AppleMusic,
  851. Deezer,
  852. Tidal,
  853. Ted,
  854. Pbs,
  855. Odysee,
  856. Playeur,
  857. Bitchute,
  858. Podbean,
  859. Rss,
  860. AmazonMusic,
  861. // Rumble,
  862. ].map((Service) => new Service());
  863. const previewPopup = new PreviewPopup();
  864. new LinkHover(services, previewPopup.showPopup.bind(previewPopup));
  865. }
  866.  
  867. if (window.top == window.self) {
  868. run();
  869. }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址