hababr
Junior Member | Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору
Код: {"YouTube":{"useimg":1,"link":"^(?:(?:(?:(?:\\w+\\.)?[yY][oO][uU][tT][uU][bB][eE](?:-nocookie|kids)?\\.com|youtube\\.googleapis\\.com)/(?:.*?\\#/)?(?:(?:(?:v|embed|e|shorts)/(?!videoseries|live_stream))|(?:(?:(?:watch|movie)(?:_popup)?(?:\\.php)?/?)?(?:\\?|\\#!?)(?:.*?[&;])??v=)))|youtu\\.be/)([0-9A-Za-z_-]{11})(?:\\?(thumb\\b))?(?:[?&](?:star)?t=(\\d+(?:\\.\\d+)?)s?)?(?:&end=(\\d+(?:\\.\\d+)?)s?)?.*$","ci":1,"url":"https://www.youtube.com/embed/$1?$2&$3&$4","res":":\n// config options\nvar config = {\n thumbFirst: true, // show thumbnail first, video second\n returnDislikes: true, // get dislikes and rating from ReturnYouTubeDislike.com\n useSidebar: true, // show description at the left of the image\n}\n\nconfig.thumbFirst = config.thumbFirst || $[2]\n\nvar d, e, f = {}, g = false, cipher, decsig, o, mfr, ps, basejs\nwindow.imagusCache = window.imagusCache || {}\n\nbaseJsUrl = 'https://www.youtube.com' + JSON.parse($._.match(/\\\"[^\\\"]+player_ias[^\\\"]+\\/base.js\\\"/)[0])\n\nif (window.imagusCache[baseJsUrl]) {\n basejs = window.imagusCache[baseJsUrl];\n} else {\n x = new XMLHttpRequest\n x.open('GET', 'https://www.youtube.com' + JSON.parse($._.match(/\\\"[^\\\"]+player_ias[^\\\"]+\\/base.js\\\"/)[0]), false)\n x.send()\n basejs = x.responseText\n window.imagusCache[baseJsUrl] = basejs\n}\n\nconst escapeRegExp = s => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\nconst parseunthrottle = data => {\n const fnnameresult = /\\.get\\(\"n\"\\)\\)&&\\(b=([^(]+?)(?:\\[(\\d+)\\])?\\([a-zA-Z0-9]\\)/.exec(data)\n var fnname = fnnameresult[1]\n if (fnnameresult[2]) fnname = new RegExp('var ' + escapeRegExp(fnname) + '\\\\s*=\\\\s*\\\\[(.+?)\\\\][,;]').exec(data)[1].split(',')[parseInt(fnnameresult[2])]\n const _argnamefnbodyresult = new RegExp(escapeRegExp(fnname) + '=function\\\\((.+?)\\\\){(.+?return b\\\\.join\\\\(\"\"\\\\))};', 's').exec(data)\n const [_, argname, fnbody] = _argnamefnbodyresult\n return new Function([argname], fnbody)\n}\nunthrottle = window.imagusCache[baseJsUrl + '_unthrottle'] || parseunthrottle(basejs)\nwindow.imagusCache[baseJsUrl + '_unthrottle'] = unthrottle\n\nfunction nt(s) {\n var u = new URL(s)\n var p = u.searchParams\n var n = p.get('n')\n if (!n) return s\n p.set('n', unthrottle(n))\n u.search = p.toString()\n return u.toString()\n}\n\nconst api_key = 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'\nconst client_ver = '2.20211221.00.00'\nconst sigtime = Number(basejs.match(/signatureTimestamp\\s*:\\s*(\\d+)/)[1])\nconst vid = $[1]\nfunction player_response(embed) {\n x.open('POST', 'https://www.youtube.com/youtubei/v1/player?key=' + api_key, false)\n x.setRequestHeader('Content-Type', 'application/json')\n var data = {\n context: {\n client: {\n clientName: 'WEB',\n clientVersion: client_ver\n }\n },\n videoId: vid,\n playbackContext: {\n contentPlaybackContext: {\n signatureTimestamp: sigtime,\n html5Preference: 'HTML5_PREF_WANTS'\n }\n },\n contentCheckOk: true,\n racyCheckOk: true\n }\n if (embed === 'agegate') data.context.client.clientScreen = 'EMBED'\n if (embed === 'embed') data.context.client = { clientName: 'TVHTML5_SIMPLY_EMBEDDED_PLAYER', clientVersion: '2.0' }\n if (embed) data.context.thirdParty = { embedUrl: 'https://www.youtube.com/' }\n x.send(JSON.stringify(data))\n o = JSON.parse(x.responseText)\n mfr = o.microformat || mfr\n ps = o.playabilityStatus\n return ps.status === 'OK'\n}\n\nfunction getDislikes(id) {\n try {\n x.open('GET', `https://returnyoutubedislikeapi.com/Votes?videoId=${id}`, false)\n x.send()\n return JSON.parse(x.responseText)\n } catch (e) {}\n}\n\nconst prepResult = (res, content) => {\n if (config.useSidebar) {\n content = content.replace(/https?:\\/\\/[\\w\\.\\/?=&+\\-]+/g, '<a href=\"$&\" target=\"_blank\">$&</a>')\n res[0][1] = `<imagus-extension type=\"sidebar\">${content}</imagus-extension>`\n this.TRG.IMGS_ext_data = res\n return { loop: 'imagus://extension' }\n } else {\n content = content.replace(/https?:\\/\\/[\\w\\.\\/?=&+\\-]+/g, '')\n content = content.replace(/[\\s\\n]*\\n[\\s\\n]*/g, ' | ')\n res[0][1] = content\n return res\n }\n}\n\nfor (let i of [null, 'embed', 'agegate']) if (player_response(i)) break\n\nif (['ERROR', 'LOGIN_REQUIRED'].indexOf(ps.status) != -1) return [ps.errorScreen.playerErrorMessageRenderer.thumbnail.thumbnails[0].url, '[' + ps.status + ', ' + ps.reason + ']']\nvar q = o.videoDetails, r = mfr.playerMicroformatRenderer\nvar title = q.title, lenSec = parseInt(q.lengthSeconds), sText = r.title.simpleText, h = r.thumbnail.thumbnails[0].url\nvar ss = lenSec % 60, mm = (lenSec - ss) / 60 % 60, hh = (lenSec - ss - mm * 60) / 3600\nvar lenStr = (hh === 0 ? '' : hh + ':') + ('0' + mm).slice(-2) + ':' + ('0' + ss).slice(-2)\nvar rt = Number(q.averageRating)\nvar dl = config.returnDislikes ? getDislikes(q.videoId) : undefined\nif (dl?.rating) {\n var colorShift = Math.round(120 * Math.max(0, dl.rating - 3) / 2) // rates from 0 to 3 will be red; from 3 to 5: scale from red to green\n var rateStyle = `background-color: hsl(${colorShift} 100% 31%); padding: 0 3px 0 1px; border-radius: 2px; color: white; height: 18px; display: inline-block`\n dl.rating = `<span style=\"${rateStyle}\">⭐${Math.round(dl.rating * 100) / 100}</span>`\n}\nvar descr = [\n `<h3>${title}</h3>`,\n title !== sText && sText,\n 'Author:\\t' + q.author,\n 'Date:\\t' + r.publishDate,\n 'Length:\\t' + lenStr,\n 'Views:\\t' + (q.viewCount | 0).toLocaleString(),\n dl ? `Rating:\\t${dl.rating} | 👍🏻${dl.likes?.toLocaleString()} | 👎🏻${dl.dislikes?.toLocaleString()} <br>(by returnyoutubedislike.com)` : '',\n '\\n' + q.shortDescription\n].filter(Boolean).join('\\n')\n\nif (ps.status !== 'OK') return prepResult([[h, ''], [h, '']], `<h3>${ps.status} - ${ps.reason}</h3><br>` + descr)\n\nvar fs = o.streamingData.formats\nif (!fs) return prepResult([[h, ''], [h, '']], '<h3>No suitable formats, probably livestreaming</h3><br>' + descr)\nfs.forEach(function (format) {\n if (format.url) { f[format.itag] = nt(format.url); return; }\n cipher = new URLSearchParams(format.signatureCipher)\n if (cipher.get('sig')) { f[format.itag] = `${cipher.get('url')}&signature=${cipher.get('sig')}`; return; }\n g = true\n})\nif (g) {\n try {\n descr = '*' + descr\n const parseDecsig = data => {\n if (data.startsWith('var script')) {\n // they inject the script via script tag\n const obj = {}\n const document = {\n createElement: () => obj,\n head: { appendChild: () => { } }\n }\n eval(data)\n data = obj.innerHTML\n }\n const fnnameresult = /=([a-zA-Z0-9\\$]+?)\\(decodeURIComponent/.exec(data)\n const fnname = fnnameresult[1]\n const _argnamefnbodyresult = new RegExp(escapeRegExp(fnname) + '=function\\\\((.+?)\\\\){(.+?)}').exec(data)\n const [_, argname, fnbody] = _argnamefnbodyresult\n const helpernameresult = /;(.+?)\\..+?\\(/.exec(fnbody)\n const helpername = helpernameresult[1]\n const helperresult = new RegExp('var ' + escapeRegExp(helpername) + '={[\\\\s\\\\S]+?};').exec(data)\n const helper = helperresult[0]\n return new Function([argname], helper + '\\n' + fnbody)\n }\n decsig = parseDecsig(basejs)\n } catch (ex) {\n console.error(ex)\n }\n fs.forEach(function (format) {\n if (f[format.itag]) return\n cipher = new URLSearchParams(format.signatureCipher)\n f[format.itag] = nt(`${cipher.get('url')}&${cipher.get('sp') || 'signature'}=${decsig(cipher.get('s'))}`)\n })\n}\ne = f[37] || f[22] || f[18] || f[59] || f[78] || f[46] || f[45] || f[44] || f[43]\nd = f[18] || f[59] || f[78] || f[46] || f[45] || f[44] || f[43] || f[37] || f[22]\nvar ct = ($[3] ? '#t=' + $[3] + ($[4] ? ',' + $[4] : '') + '&' : '') + '#mp4'\ne = e ? (d === e ? [[e + ct, title]] : [[e + ct, title], [d + ct, title]]) : e\nvar res = e ? (config.thumbFirst ? [[h, title]].concat(e) : e.concat([[h, title]])) : [h, title]\n\nreturn prepResult(res, descr)","img":"^i(?:\\d|mg)?\\.ytimg\\.com/(?:vi|an_webp)[^/]*/([\\w\\-]{11})/(?:[\\w]+?)\\.(?:\\w+)","loop":2,"to":"www.youtube.com/embed/$1?thumb","note":"Dulus_No + Imagus_fan (fix)\nhttps://www.reddit.com/r/imagus/comments/14fyk2l/comment/jp7acpg\nOLD\nhttps://www.reddit.com/r/imagus/comments/uldmok/comment/iswnqal\nhttps://www.reddit.com/r/imagus/comments/r19duk/super_slow_youtube_loading_and_buffering_time_on/hlyczin\n\n\nПРИМЕРЫ / EXAMPLES\nhttps://www.reddit.com/domain/youtube.com/\nhttps://www.reddit.com/r/imagus/comments/r19duk/comment/hxfd2pa"}} |
Код: {"[HLS_Extension]":{"link":"^imagus:\\/\\/extension","loop":1,"url":":'data:,' + this.TRG.IMGS_c_resolved.URL","res":":\nfunction inject(tag, id, content, parent) {\n const elem = document.createElement(tag)\n elem.setAttribute('id', id)\n elem.textContent = content\n return parent ? parent.appendChild(elem) : document.head.appendChild(elem)\n}\n\nfunction override(that) {\n that.reset_original = that.reset\n that.reset = preventImmediateHover => {\n if (that.EXTENSION.VIME) {\n that.EXTENSION.VIME.remove()\n delete that.EXTENSION.VIME\n }\n\n if (that.EXTENSION.VIDEOJS) {\n that.EXTENSION.VIDEOJS.player.dispose()\n that.EXTENSION.VIDEOJS.remove()\n delete that.EXTENSION.VIDEOJS\n }\n\n return that.reset_original(preventImmediateHover)\n }\n\n platform.onkeydown_original = platform.onkeydown\n platform.onkeydown = e => {\n const url = that.TRG.IMGS_ext?.attributes['url']?.textContent\n if (url && [ e.code.replace(/^Key/, ''), e.key ].includes(cfg.keys.hz_open)) {\n Port.send({ cmd: 'open', url: url, nf: e.shiftKey })\n } else {\n return platform.onkeydown_original(e)\n }\n }\n\n that.fzClickAct_original = that.fzClickAct\n that.fzClickAct = e => !that.EXTENSION.contains(e.target) ? that.fzClickAct_original(e) : undefined\n\n that.prepareCaption_original = that.prepareCaption\n that.prepareCaption = (trg, caption) => {\n const m = caption.match(/(<imagus-extension.+<\\/imagus-extension>)?(.*)/s)\n const e = () => { const t = document.createElement('template'); t.innerHTML = m[1]; return t.content.firstChild; }\n trg.IMGS_ext = trg.IMGS_ext_from_url || m[1] ? e() : undefined\n return that.prepareCaption_original(trg, m[2])\n }\n\n that.show_original = that.show\n that.show = (msg, delayed) => {\n if (msg === 'load') {\n if (that.EXTENSION.VIDEOJS) {\n return\n } else {\n return that.show_original(msg, delayed)\n }\n }\n\n const ext = that.TRG.IMGS_ext\n that.EXTENSION.style.display = ext ? 'block' : 'none'\n that.DIV.style.transform = ''\n\n const res = that.show_original(msg, delayed)\n\n if (ext) {\n const type = ext.getAttribute('type').split('-')\n const url = ext.getAttribute('url')\n that.EXTENSION.className = 'imagus-' + type[0]\n that.EXTENSION_custom_style.textContent = ext.getAttribute('custom-style')\n\n switch (type[0]) {\n case 'sidebar':\n that.EXTENSION.innerHTML = ext.innerHTML\n that.EXTENSION.scrollTop = 0\n const rect = that.EXTENSION.getBoundingClientRect()\n const tfX = rect.left < 0 ? -rect.left : rect.right > window.innerWidth ? window.innerWidth - rect.right : 0\n const tfY = rect.top < 0 ? -rect.top : rect.bottom > window.innerHeight ? window.innerHeight - rect.bottom : 0\n if (tfX || tfY)\n that.DIV.style.transform = `translate(${tfX}px, ${tfY}px)`\n break\n\n case 'banner':\n that.EXTENSION.innerHTML = `\n <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"${url}\">\n <svg viewBox=\"0 0 360 96\">\n <foreignObject width=\"100%\" height=\"100%\">\n <div>\n <span>${ext.getAttribute('text')}</span>\n </div>\n </foreignObject>\n </svg>\n </a>\n `.replace(/\\n\\s*/g, '')\n break\n\n case 'videojs':\n if (!url || that.EXTENSION.VIDEOJS) {\n break\n }\n\n if (!that.EXTENSION.VIDEOJS_status) {\n that.EXTENSION.VIDEOJS_status = 'loading'\n console.time('Load Video.js')\n\n if (typeof loadVideoJS === 'function') {\n loadVideoJS()\n that.EXTENSION.VIDEOJS_status = 'loaded'\n console.timeEnd('Load Video.js')\n createPlayer()\n break\n }\n\n const urlsCSS = [\n 'https://cdn.jsdelivr.net/npm/video.js@7.20.3/dist/video-js.min.css',\n 'https://cdn.jsdelivr.net/npm/videojs-max-quality-selector@0.9.1/dist/videojs-max-quality-selector.css'\n // 'https://unpkg.com/video.js@7.20.3/dist/video-js.min.css',\n // 'https://unpkg.com/videojs-max-quality-selector@0.9.1/dist/videojs-max-quality-selector.css'\n ]\n\n const urlsJS = [\n 'https://cdn.jsdelivr.net/npm/video.js@7.20.3/dist/video.min.js',\n 'https://cdn.jsdelivr.net/npm/videojs-contrib-quality-levels@2.2.0/dist/videojs-contrib-quality-levels.min.js',\n 'https://cdn.jsdelivr.net/npm/videojs-max-quality-selector@0.9.1/dist/videojs-max-quality-selector.min.js'\n // 'https://unpkg.com/video.js@7.20.3/dist/video.min.js',\n // 'https://unpkg.com/videojs-contrib-quality-levels@2.2.0/dist/videojs-contrib-quality-levels.min.js',\n // 'https://unpkg.com/videojs-max-quality-selector@0.9.1/dist/videojs-max-quality-selector.min.js'\n ]\n\n const urlGet = (url) => new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest()\n\n xhr.onload = () => {\n if (xhr.status === 200) {\n console.log(xhr.statusText, '|', url)\n resolve(xhr.responseText.replace(/\\n?\\/\\*.+?\\*\\/\\n?/gs, '').trim())\n } else {\n console.error(xhr.statusText, '|', url)\n reject(xhr.statusText)\n }\n }\n\n xhr.onerror = () => {\n console.error(xhr.statusText, '|', url)\n reject(xhr.statusText)\n }\n\n xhr.open('GET', url)\n xhr.send()\n })\n\n const pCSS = Promise.all(urlsCSS.map(urlGet)).then((result) => {\n document.head.insertAdjacentHTML('beforeend', `<style>${result.map(i => i.replace(/^@charset.+?;/, '')).join('')}</style>`)\n })\n\n const pJS = Promise.all(urlsJS.map(urlGet)).then((result) => {\n Function(result[0]\n .replace(\n 'new Uint8Array(t.data.data,i.byteOffset||0,i.byteLength||t.data.data.byteLength)',\n '/firefox/i.test(window.navigator.userAgent)?cloneInto(new Uint8Array(t.data.data,i.byteOffset||0,i.byteLength||t.data.data.byteLength),window):new Uint8Array(t.data.data,i.byteOffset||0,i.byteLength||t.data.data.byteLength)'\n )\n .replace(\n 'n[t].forEach', // _this6[idName].forEach\n 'structuredClone(n[t]).forEach' // https://caniuse.com/mdn-api_structuredclone\n ) + result.slice(1).join(''))()\n })\n\n Promise.all([ pCSS, pJS ]).then(() => {\n that.EXTENSION.VIDEOJS_status = 'loaded'\n console.timeEnd('Load Video.js')\n createPlayer()\n })\n }\n\n if (that.EXTENSION.VIDEOJS_status === 'loaded') {\n createPlayer()\n }\n\n function createPlayer() {\n that.EXTENSION.VIDEOJS = document.createElement('video')\n that.EXTENSION.VIDEOJS.setAttribute('class', 'video-js')\n that.EXTENSION.VIDEOJS.setAttribute('id', 'imagus-player')\n that.EXTENSION.appendChild(that.EXTENSION.VIDEOJS)\n\n const playerOptions = {\n autoplay: 'any',\n controls: true,\n loop: true,\n preload: 'auto'\n }\n\n videojs(that.EXTENSION.VIDEOJS, playerOptions, () => {\n const player = that.EXTENSION.VIDEOJS.player = videojs.players['imagus-player']\n const qLevels = player.qualityLevels()\n const mqSelectorOptions = {\n autoLabel: 'Auto ',\n defaultQuality: 2,\n disableAuto: true,\n displayMode: 1,\n filterDuplicateHeights: false,\n filterDuplicates: false,\n showBitrates: true\n }\n const mqSelector = player.maxQualitySelector(mqSelectorOptions)\n\n const setSVG = (width, height) => {\n if (!player.isFullscreen()) {\n that.set(`data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\"></svg>`)\n if (cfg.hz.capWH) {\n that.CAP.children[1].textContent = `${width}\\u00d7${height}`\n }\n }\n }\n\n qLevels.on('change', (e) => {\n setSVG(qLevels[qLevels.selectedIndex].width, qLevels[qLevels.selectedIndex].height)\n })\n\n player.on('resize', () => {\n const vWidth = player.videoWidth()\n const vHeight = player.videoHeight()\n player.width(vWidth)\n player.height(vHeight)\n setSVG(vWidth, vHeight)\n })\n\n player.on('fullscreenchange', () => {\n if (!mqSelector.selectedIndexPrevious) {\n mqSelector.selectedIndexPrevious = mqSelector.selectedIndex\n mqSelector.options.disableAuto = false\n mqSelector.changeLevel(-1) // auto\n } else {\n mqSelector.changeLevel(mqSelector.selectedIndexPrevious)\n delete mqSelector.selectedIndexPrevious\n }\n })\n\n player.volume(cfg.hz.mediaVolume / 100)\n player.src(url)\n })\n }\n\n break\n\n }\n }\n\n return res\n }\n\n that.switchToHiResInFZ_original = that.switchToHiResInFZ\n that.switchToHiResInFZ = () => {\n if (that.fullZm === 1) {\n that.EXTENSION.style.pointerEvents = 'auto'\n if (that.EXTENSION.VIME || that.EXTENSION.VIDEOJS)\n that.EXTENSION.style.zIndex = 'auto'\n }\n if (that.fullZm === false) {\n that.EXTENSION.style.pointerEvents = ''\n that.EXTENSION.style.zIndex = ''\n }\n return that.switchToHiResInFZ_original()\n }\n}\n\nif (!this.EXTENSION) {\n const style = `\n #imagus-extension {\n pointer-events: none;\n }\n\n #imagus-extension.imagus-sidebar {\n background: padding-box rgb(255, 255, 255);\n border: ${this.DIV.style.border};\n border-radius: ${this.DIV.style.borderRadius};\n box-shadow: ${this.DIV.style.boxShadow};\n box-sizing: border-box;\n color: black;\n font: 14px / 1.3 sans-serif;\n left: calc(-360px + ${this.DIV.style.borderWidth} - 1px);\n padding: 5px 8px 6px;\n position: absolute;\n top: -${this.DIV.style.borderWidth};\n white-space: pre-wrap;\n width: 360px;\n z-index: -1;\n max-height: calc(100% + ${this.DIV.style.borderWidth} * 2);\n overflow-y: auto;\n overflow-wrap: break-word;\n }\n #imagus-extension.imagus-sidebar > b {\n font-weight: bold;\n }\n\n #imagus-extension.imagus-banner {\n position: relative;\n top: -20%;\n }\n #imagus-extension.imagus-banner div {\n display: table;\n height: 100%;\n width: 100%;\n }\n #imagus-extension.imagus-banner span {\n color: white;\n display: table-cell;\n font: 18px sans-serif;\n vertical-align: middle;\n text-align: center;\n white-space: pre-wrap;\n }\n #imagus-extension.imagus-banner:hover span {\n color: #cceeff;\n text-decoration: underline;\n }\n\n #imagus-extension.imagus-videojs {\n height: 100%;\n position: relative;\n top: -100%;\n z-index: -1;\n }\n #imagus-extension.imagus-videojs > #imagus-player {\n --bottom: 0;\n --left: 0;\n --margin: auto;\n --position: absolute;\n --right: 0;\n --top: 0;\n height: 100%;\n width: 100%;\n }\n #imagus-extension .vjs-max-quality-selector-button .vjs-menu {\n width: 12em;\n }\n #imagus-extension .vjs-max-quality-selector-button .vjs-menu .vjs-menu-content {\n padding: 5px;\n }\n `.replace(/\\n\\s*/g, '')\n this.EXTENSION = inject('div', 'imagus-extension', null, this.DIV)\n inject('style', 'imagus-extension-style', style)\n this.EXTENSION_custom_style = inject('style', 'imagus-extension-custom-style')\n override(this)\n}\n\nconst url = new URL($[0])\nif (url.search) {\n const elem = document.createElement('imagus-extension')\n url.searchParams.forEach((val, key) => elem.setAttribute(key, val))\n this.TRG.IMGS_ext_from_url = elem\n}\n\nreturn this.TRG.IMGS_ext_data","note":"64h + Release-Revolution (fix)\nhttps://www.reddit.com/r/imagus/comments/15dwhc7/comment/ju6si43\nOLD\nhttp://forum.ru-board.com/topic.cgi?forum=5&topic=50874&start=1484&limit=1&m=1#1\nhttp://forum.ru-board.com/topic.cgi?forum=5&topic=50874&start=1379&limit=1&m=1#1\n\n\n!!!\nДинамически загружаемый Video.js (https://github.com/videojs/video.js), поддерживающий HLS, DASH и выбор разрешения.\n+\nДля остановки зацикливания видео - установить в параметре loop значение \"false\":\nhttps://i.imgur.com/iMVa3OF.png\n==\nDynamically loaded Video.js (https://github.com/videojs/video.js), supporting HLS, DASH and resolution selection.\nTo stop video looping, set the loop parameter to \"false\":\nhttps://i.imgur.com/iMVa3OF.png\n+\nБоковая панель с доп.инфо для некоторых сайтов (IMDB, YouTube)./ Side panel with add.info for some sites (IMDB, YouTube):\nhttps://i.imgur.com/se6MwJ6.png\n\nПРИМЕРЫ / EXAMPLES\nhttp://forum.ru-board.com/topic.cgi?forum=5&topic=50874&start=1480#6\nhttp://forum.ru-board.com/topic.cgi?forum=5&topic=50874&start=1240#9"}} |
|