您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Coursera SubEx: 根据你的选择,同时显示多种语言的字幕显示在coursera.org课程学习页面的视频播放器下方。 不占用视频内容区域,还可以方便拷贝字幕做笔记。
当前为
// ==UserScript== // @name Coursera SubEx / Coursera multiple subtitles show below the video / Coursera多字幕显示在视频下方插件 // @namespace http://tampermonkey.net/ // @version 0.9 // @description Coursera SubEx , Show multiple subtitles/captions of any languge below the video in coursera.org's learning page at your wish. // @description:zh-CN Coursera SubEx: 根据你的选择,同时显示多种语言的字幕显示在coursera.org课程学习页面的视频播放器下方。 不占用视频内容区域,还可以方便拷贝字幕做笔记。 // @description:zh-TW Coursera SubEx: 根據你的選擇,同時顯示多種語言的字幕顯示在coursera.org課程學習頁面的視頻播放器下方。 不占用視頻內容區域,還可以方便拷貝字幕做筆記。 // @author DryTofu // @match *://www.coursera.org/learn/* // @match *://coursera.org/learn/* // @icon https://www.google.com/s2/favicons?sz=64&domain=coursera.org // @require https://cdn.bootcss.com/jquery/1.11.1/jquery.min.js // @license MIT // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; // coursera字幕处理 // 参考 https://stackoverflow.com/questions/32252337/how-to-style-text-tracks-in-html5-video-via-css/45087610#45087610 // https://stackoverflow.com/questions/64505385/html5-video-subtitles-positioning let videoCss = ` .ex-subtitle-line-wrap { font-size: 24px; } .ex-subtitle-line { margin: 5px auto; text-align: center; border: 1px dashed #aaa; } .ex-subtitle-line span { padding-right: 6px; padding-left: 6px; } .ex-subtitle-line .subEx_beforeSubItem { color: #ccc; } .ex-subtitle-line .subEx_currentSubItem { background-color: #afe9f7; color: #111; } .ex-subtitle-line .subEx_afterSubItem { color: #ccc; } .exLngToolbar { margin-top: 10px; } .exLngToolbar label { font-weight: normal; padding: 5px 10px 5px 5px; background-color: transparent; } .exLngToolbar label.selected { font-weight: bold; background-color: #aaa; } .exLngToolbar button{ margin-left: 10px; padding: 0px 5px; } #ym_subExBtnWrap { position: absolute; left: 0px; top: 0px; z-index: 9999; } .ym_subExBtn { background-color: #34A853; color: #fff; border: none; padding: 2px 5px; margin-right: 8px; } `; (function(factory) { factory(document, jQuery) })(function (document, $) { GM_addStyle(videoCss) const Setting = (function() { let selectedLngs = null, keepSubBeforeCount = null, keepSubAfterCount = null; const DEFAULT_SELECTED_LNGS = [], DEFAULT_SUB_FONT_SIZE = '24px', DEFAULT_KEEP_SUB_BEFORE_COUNT = 0, DEFAULT_KEEP_SUB_AFTER_COUNT = 0; const LS_KEY_SEL_LNG = "coursera_video_ext_selected_lngs", LS_KEY_SUB_FONT_SIZE = "coursera_video_ext_sub_font_size", LS_KEY_KEEP_SUB_BEFORE_COUNT = "coursera_video_ext_keep_sub_before", LS_KEY_KEEP_SUB_AFTER_COUNT = "coursera_video_ext_keep_sub_after";; function getIntValFromLs(key, defaultVal) { let n = parseInt(localStorage.getItem(key)) if(isNaN(n)) { return defaultVal } return n; } function saveIntValToLs(key, val, defaultVal) { let intVal = parseInt(val) if(isNaN(intVal)) { intVal = defaultVal } localStorage.setItem(key, intVal) return intVal } function getArrayValFromLs(key, defaultVal) { let arr = null try { arr = JSON.parse(localStorage.getItem(key)); } catch(e) { } if(!arr) { arr = defaultVal } return arr } function saveArrayValToLs(key, val, defaultVal) { localStorage.setItem(key, JSON.stringify(val)) return val } function getSubFontSize() { return localStorage.getItem(LS_KEY_SUB_FONT_SIZE) || DEFAULT_SUB_FONT_SIZE; } function saveSubFontSize(fontSize) { localStorage.setItem(LS_KEY_SUB_FONT_SIZE, fontSize) } function getKeepSubBeforeCount() { if(keepSubBeforeCount === null) { keepSubBeforeCount = getIntValFromLs(LS_KEY_KEEP_SUB_BEFORE_COUNT, DEFAULT_KEEP_SUB_BEFORE_COUNT); } return keepSubBeforeCount; } function saveKeepSubBeforeCount(count) { keepSubBeforeCount = saveIntValToLs(LS_KEY_KEEP_SUB_BEFORE_COUNT, count, DEFAULT_KEEP_SUB_BEFORE_COUNT) } function getKeepSubAfterCount() { if(keepSubAfterCount === null) { keepSubAfterCount = getIntValFromLs(LS_KEY_KEEP_SUB_AFTER_COUNT, DEFAULT_KEEP_SUB_AFTER_COUNT); } return keepSubAfterCount; } function saveKeepSubAfterCount(count) { keepSubAfterCount = saveIntValToLs(LS_KEY_KEEP_SUB_AFTER_COUNT, count, DEFAULT_KEEP_SUB_AFTER_COUNT) } function removeElm(arr, elm) { let i = arr.indexOf(elm) if(i > -1) { arr.splice(i, 1) } return arr // return arr.filter(l => l != elm) } function getSelectedLngs() { if(!selectedLngs) { selectedLngs = getArrayValFromLs(LS_KEY_SEL_LNG, DEFAULT_SELECTED_LNGS) } return selectedLngs } function saveSelectedLngs(arr) { selectedLngs = saveArrayValToLs(LS_KEY_SEL_LNG, arr, DEFAULT_SELECTED_LNGS) return selectedLngs } function addLng(lng) { if(!lng) { return } if(!selectedLngs) { selectedLngs = getSelectedLngs() } if(!selectedLngs.includes(lng)) { selectedLngs.push(lng) saveSelectedLngs(selectedLngs) } return selectedLngs } function removeLng(lng) { if(!lng) { return } if(!selectedLngs) { selectedLngs = getSelectedLngs() } selectedLngs = removeElm(selectedLngs, lng) saveSelectedLngs(selectedLngs) return selectedLngs } function clearSelectedLngs() { selectedLngs = [] saveSelectedLngs(selectedLngs) return selectedLngs } return { getSelectedLngs, clearSelectedLngs, removeLng, addLng, saveSelectedLngs, getSubFontSize, saveSubFontSize, getKeepSubBeforeCount, saveKeepSubBeforeCount, getKeepSubAfterCount, saveKeepSubAfterCount, } })() // global objects 全局变量 // $videoWrapper 是包含video的比较上级的div let $video = null, video = null, showMode, $videoWrapper = null; let $extSubtitleWrap = null, $exLngToolbar = null; let subtitleLineMap = {}; // 语言string到字幕显示区jQuery对象的映射表 // if (i.language == "zh-CN" || i.language == "zh-TW" || i.language == "en-US" || i.language == "en") { const lngWeightMap = { "zh-CN": 10, "zh": 20, "en-US": 30, "en-GB": 40, "en": 50, "zh-TW": 60, } const lngWeight = function(lng) { return lngWeightMap[lng] || 1000 } function printTracks(msg, $tracks) { let arr = [] $tracks.forEach($track => { arr.push($track.attr('srclang') + '-' + $track.attr('label') + '-' + $track.data('lngweight')) }) console.log(msg, arr) } function printTextTracks(msg, textTracks) { for(let i=0; i<textTracks.length; ++i) { let track = textTracks[i]; let cues = track.cues; let cuesLen = cues ? cues.length : 0; console.log('track' + i, track, cuesLen) } } function textTracksToLngs(textTracks) { let lngs = [] for (const track of textTracks) { lngs.push(track.language) } return lngs; } // 把 textTracks 按照语言权重排序 生成简单对象数组 function textTracksToSortedObjs(textTracks) { let objs = [] for (const track of textTracks) { objs.push({ language: track.language, label: track.label, lngweight: lngWeight(track.language), }) } objs.sort(function(a, b) { return a.lngweight - b.lngweight }) return objs; } // 检查已开选中字幕语言和当前视频存在的字幕语言,去掉当前视频不存在的,并保存到localstorage,返回选中语言和当前可用语言的交集 function checkSelectedLngsValid(selectedLngs, textTracks) { const allLngs = textTracksToLngs(textTracks) // 当前视频的全部可用字幕语言 const notValidLngs = selectedLngs.filter(lng => !allLngs.includes(lng)) // 找出selectedLngs含有,但当前视频不存在的字幕语言 if(notValidLngs && notValidLngs.length) { notValidLngs.forEach(lng => Setting.removeLng(lng)) } return Setting.getSelectedLngs() } // 根据选中的语言,处理video中的textTracks 和 生成对应的字幕栏 function applySelectedLngs(selectedLngs, textTracks, $extSubtitleWrap) { subtitleLineMap = {} $extSubtitleWrap.find(">.ex-subtitle-line").each(function() { // 先把现有字幕栏缓存起来,并且从父节点删除 let $subtitleLine = $(this) // 这是显示一种字幕语言的div let lng = $subtitleLine.attr("data-lng") if(lng) { // 尝试把去掉的字幕栏也暂存起来,如果出现奇怪错误,就用后面的判断语句 subtitleLineMap[lng] = $subtitleLine /* if(selectedLngs.includes(lng)) { subtitleLineMap[lng] = $subtitleLine } */ } $subtitleLine.remove() }) selectedLngs.forEach(lng => { // 按照当前选中语言初始化各个语言的字幕栏 let $subtitleLine = subtitleLineMap[lng] if(!$subtitleLine) { $subtitleLine = $(`<div class="ex-subtitle-line cds-1 css-0 cds-3 cds-grid-item cds-48">${lng}</div>`).attr('data-lng', lng) subtitleLineMap[lng] = $subtitleLine } $extSubtitleWrap.append($subtitleLine) }) if(selectedLngs && selectedLngs.length) { // 有选中字幕才处理 // 先设置 track的mode属性 hidden 和 disabled , hidden是激活但不在video中显示的字幕轨 for (const track of textTracks) { if(selectedLngs.includes(track.language)) { track.mode = 'hidden' } else { track.mode = 'disabled' } } // 绑定 cue事件 因为大多数时候不是把track激活,就能拿到cues的,需要加载,所以这里用了重试循环任务 let maxTryTime = 1000, tryTime = 0; let bindCueEvent = function() { let cueLoadFlag = true; // 所有选中字幕轨的cues是否都已经加载成功的标志 ++tryTime // console.log("开始尝试第" + tryTime + "次------->") for (const track of textTracks) { // 循环字幕track let trackLng = track.language if(selectedLngs.includes(trackLng)) { // 选中语言包含这个字幕,需要处理的字幕轨 let cues = track.cues if(cues && cues.length) { // 本字幕轨的cues 加载成功! // console.log("尝试" + tryTime + "次:" + trackLng + '--成功找到cues:' + cues.length, cues) for (let j=0; j<cues.length; ++j) { let cue = cues[j] // console.log('cues[' + j + "]", cue) // 设置字幕中一条字幕cue的事件 cue.onenter = function() { applyCurrentSub(trackLng, cues, j) }; cue.onexit = function() { // console.log(trackLng + ' 字幕退出:' + this.text) }; } } else { // 这个字幕轨的cues没有加载,设置标志为false,等待下次定时任务执行 // console.log("XX-尝试" + tryTime + "次:" + trackLng + '--找到空字幕:' + cues.length, cues) cueLoadFlag = false } } } if(cueLoadFlag) { // 如果cues都装载上了 , 需要处理当前字幕显示 // 判断当前视频所处时间点加载当前字幕 let currentTime = video.currentTime console.log('所选语言字幕轨全部加载完毕,video.currentTime=' + video.currentTime) for (const track of textTracks) { // 循环字幕track let trackLng = track.language if(selectedLngs.includes(trackLng)) { // 选中语言包含这个字幕,需要处理的字幕轨 let cues = track.cues if(cues && cues.length) { // 本字幕轨的cues 加载成功! let matchIdx = findMatchCue(currentTime, cues, trackLng) applyCurrentSub(trackLng, cues, matchIdx) } } } } if(!cueLoadFlag && tryTime < maxTryTime) { // 如果有 cues没有正常处理,且在最大重试次数内 setTimeout(bindCueEvent, 500) } } bindCueEvent() // 执行字幕cue事件绑定 } console.log('--After applySelectedLngs', textTracks) } function findMatchCue(currentTime, cues, lng) { currentTime = currentTime || 0 for (let j=0; j<cues.length; ++j) { let cue = cues[j] if(currentTime >= cue.startTime && currentTime <= cue.endTime) { console.log('currentTime=' + currentTime + ',在语言:' + lng + "中找到匹配字幕:" + j, cue) return j } } console.log('currentTime=' + currentTime + ',在语言:' + lng + "中没有找到匹配字幕,返回0") return 0 } // 根据video当前播放时间显示字幕 // lng : 当前语言 // cues: 当前cues // j: 当前所处字幕index function applyCurrentSub(lng, cues, j) { // console.log(trackLng + ' 字幕进入:' + this.text) let beforeCount = Setting.getKeepSubBeforeCount(), afterCount = Setting.getKeepSubAfterCount(); // console.log('----j=' + j) let beforeTexts = [] if(beforeCount > 0) { let beforeFromIdx = j - beforeCount if(beforeFromIdx < 0) { beforeFromIdx = 0; } for(let k=beforeFromIdx; k<j; ++k) { beforeTexts.push('<span class="subEx_beforeSubItem">' + cues[k].text + ' </span>') } } let afterTexts = [] if(afterCount > 0) { let afterToIdx = j + afterCount + 1 if(afterToIdx > cues.length) { afterToIdx = cues.length; } for(let k=j+1; k<afterToIdx; ++k) { afterTexts.push('<span class="subEx_afterSubItem">' + cues[k].text + ' </span>') } } let subHtml = beforeTexts.join('') + '<span class="subEx_currentSubItem">' + cues[j].text + ' </span>' + afterTexts.join('') subtitleLineMap[lng].html(subHtml) } function doWork() { // 找到视频元素之后 初始化字幕和控制元素 if($extSubtitleWrap) { $extSubtitleWrap.remove() } if($exLngToolbar) { $exLngToolbar.remove() } $extSubtitleWrap = $(`<div class="cds-1 css-0 cds-3 cds-grid-item cds-48 ex-subtitle-line-wrap" id="extSubtitleWrap"></div>`); let selectedLngs = Setting.getSelectedLngs() let textTracks = video.textTracks console.log("tracks", textTracks); // 求 selectedLngs 和 textTracks 的交集,如果 selectedLngs 有textTracks中不存在的,则需要删除 (也会存储到 localstorage) selectedLngs = checkSelectedLngsValid(selectedLngs, textTracks) console.log('selectedLngs', selectedLngs) let trackInfoObjs = textTracksToSortedObjs(textTracks) console.log('trackInfoObjs', trackInfoObjs) $exLngToolbar = $('<div class="cds-1 css-0 cds-3 cds-grid-item exLngToolbar" id="exLngToolbar"></div>') trackInfoObjs.forEach(obj => { $exLngToolbar.append(`<label>${obj.label}<input type="checkbox" value="${obj.language}" /></label> `) }) // 字幕语言选择checkbox初始化和事件绑定 $exLngToolbar.find('input[type=checkbox]').each(function() { let $checkbox = $(this), lng = $checkbox.val() if(selectedLngs.includes(lng)) { $checkbox.prop('checked', true) $checkbox.closest('label').addClass('selected') } $checkbox.click(function() { if($checkbox.prop('checked')) { console.log(lng + ' 选中') selectedLngs = Setting.addLng(lng) $checkbox.closest('label').addClass('selected') } else { console.log(lng + ' 取消选中') selectedLngs = Setting.removeLng(lng) $checkbox.closest('label').removeClass('selected') } console.log('selectedLngs', selectedLngs) applySelectedLngs(selectedLngs, video.textTracks, $extSubtitleWrap) }) }) // ----- 开始 增加 font size 和 最近字幕显示多少条的配置 let exSubFontSize = Setting.getSubFontSize() let selectCtrlStrArr = ['<select name="subExSubFontSizeSel" id="subExSubFontSizeSel">'] let fontSizeList = ["0.5rem", "0.8rem", "1rem", "1.2rem", "1.5rem", "1.8rem", "2rem", "2.5rem", "3rem", "3.5rem", "4rem", "4.5rem", "5rem"] for(let i=6; i<=120; ++i) { fontSizeList.push(i + 'px') } fontSizeList.forEach(fs => { selectCtrlStrArr.push('<option value="' + fs + '"' + (fs==exSubFontSize ? ' selected' : '') + '>' + fs + '</option>') }) selectCtrlStrArr.push('</select>') let $exSubFontSizeSel = $(selectCtrlStrArr.join('')) function applySubExFontSize() { $extSubtitleWrap.css("font-size", $exSubFontSizeSel.val()) } $exSubFontSizeSel.change(function() { Setting.saveSubFontSize($(this).val()) applySubExFontSize() }) applySubExFontSize() $exLngToolbar.append($exSubFontSizeSel) let keepSubBeforeCount = Setting.getKeepSubBeforeCount() selectCtrlStrArr = ['<select name="subExkeepSubBeforeCountSel" id="subExkeepSubBeforeCountSel">'] for(let i=0; i<=10; ++i) { selectCtrlStrArr.push('<option value="' + i + '"' + (i==keepSubBeforeCount ? ' selected' : '') + '>' + i + '</option>') } selectCtrlStrArr.push('</select>') let $keepSubBeforeCountSel = $(selectCtrlStrArr.join('')) $keepSubBeforeCountSel.change(function() { Setting.saveKeepSubBeforeCount($(this).val()) }) let keepSubAfterCount = Setting.getKeepSubAfterCount() selectCtrlStrArr = ['<select name="subExkeepSubAfterCountSel" id="subExkeepSubAfterCountSel">'] for(let i=0; i<=10; ++i) { selectCtrlStrArr.push('<option value="' + i + '"' + (i==keepSubAfterCount ? ' selected' : '') + '>' + i + '</option>') } selectCtrlStrArr.push('</select>') let $keepSubAfterCountSel = $(selectCtrlStrArr.join('')) $keepSubAfterCountSel.change(function() { Setting.saveKeepSubAfterCount($(this).val()) // TODO }) let $exSubShowLastSubCountSelLabel = $('<span> Keep </span>') $exLngToolbar.append( $('<span> Keep </span>')).append($keepSubBeforeCountSel) .append('<span> : </span>').append($keepSubAfterCountSel) // ----- 结束 增加 font size 和 最近字幕显示多少条的配置 // ----- 开始 增加 mode=1 的时候增大缩小视频播放div的控制按钮 if(showMode == 1) { let $addVideoWrapBtn = $('<button>+</button>'), $reduceVideoWrapBtn = $('<button>-</button>'), stepPx = 5; let posVideoWrapperAddTimeOut = null, posVideoWrapperReduceTimeout = null, timeOutFreq = 150; $addVideoWrapBtn.mousedown(function() { posVideoWrapperAdd(stepPx, 0) posVideoWrapperAddTimeOut = setInterval(function() { posVideoWrapperAdd(stepPx, 0) }, timeOutFreq) }).on('mouseout mouseup', function() { if(posVideoWrapperAddTimeOut) { clearInterval(posVideoWrapperAddTimeOut) } posVideoWrapperAddTimeOut = null }) $reduceVideoWrapBtn.click(function() { posVideoWrapperReduce(stepPx, 0) }) $reduceVideoWrapBtn.mousedown(function() { posVideoWrapperReduce(stepPx, 0) posVideoWrapperReduceTimeout = setInterval(function() { posVideoWrapperReduce(stepPx, 0) }, timeOutFreq) }).on('mouseout mouseup', function() { // console.log('mouseout or mouseup事件触发!!!') if(posVideoWrapperReduceTimeout) { clearInterval(posVideoWrapperReduceTimeout) } posVideoWrapperReduceTimeout = null }) $exLngToolbar.append($addVideoWrapBtn).append($reduceVideoWrapBtn) } // ----- 结束 增加 mode=1 的时候增大缩小视频播放div // 语言checkbox toolbar let $toolbarWrap = $("div.rc-VideoToolbar > .cds-grid-item:first") $toolbarWrap.find("> .exLngToolbar").remove() $toolbarWrap.append($exLngToolbar) // let $videoToolbar = $("div.rc-VideoToolbar > .cds-grid-item").append($exLngToolbar) // --- 语言选择栏处理完毕 -- applySelectedLngs(selectedLngs, video.textTracks, $extSubtitleWrap) if(showMode == 1) { // 视频显示模式,浮动显示 /***************** // 这段代码在把把字幕栏和字幕语言选择工具栏放到页面下方的方法 // 把字幕栏和字幕语言选择工具栏放到页面下方 let $addPoint = $('.rc-ItemFeedback:first').parent().parent() console.log('subtitle opt addPoint:', $addPoint) $addPoint.before($extSubtitleWrap, $exLngToolbar) **********/ // 改成加在滚动内容区域的头部 let $addPoint = $('.ItemLecture_Video_Title:first') console.log('subtitle opt addPoint:', $addPoint) $addPoint.before($extSubtitleWrap, $exLngToolbar) videoFloatShow() } else { // showMode == 0 字幕栏插入到播放器下方,播放器不浮动 // 在video上层div后方插入 字幕栏和语言选择工具栏 // TODO if($videoWrapper.hasClass('ym_videoFloatWrapper')) { // 从浮动状态下来 $videoWrapper.removeClass('ym_videoFloatWrapper') $videoWrapper.removeAttr("style"); /* $videoWrapper.css({ "position": "static", "left": 'auto', "width": 'auto', 'z-index': 'auto', }) */ let $videoAddPoint = $('.ItemLecture_Video_Notes_Navigation') if(!$videoAddPoint.length) { $videoAddPoint = $('.ItemLecture_Video_Title') if(!$videoAddPoint.length) { console.error('Coursear页面结构发生变化,找不到视频插入点') } } $videoAddPoint.after($videoWrapper.remove()) $extSubtitleWrap.css('margin-top', '10px') } $videoWrapper.after($extSubtitleWrap, $exLngToolbar) } console.log("---------结束执行 YM Coursera 字幕处理 ") } let _findVideoRetryTime = 0, _findVideoRetryMaxTime = 60 function findVideo(_showMode) { showMode = _showMode ++_findVideoRetryTime $video = $("video.vjs-tech:first"); if($video.length > 0) { console.log("## 找到video") video = $video.get(0) $videoWrapper = $video.closest('.cds-grid-item') // 设置包含video的上级层全局变量 video > .video-main-player-container > .rc-VideoMiniPlayer > .cds-1 css-0 cds-3 cds-grid-item cds-48 if($videoWrapper.length == 0) { console.error("video的上级层没有找到:通过 $video.closest('.cds-grid-item'),页面可能发生变化!"); } _findVideoRetryTime = 0 // 清零,下次找新的video重新算 doWork() return } else { if(_findVideoRetryTime >= _findVideoRetryMaxTime) { console.log("-- 没有找到video元素,尝试超过最大尝试次数" + _findVideoRetryMaxTime +"次,退出! 刷新页面重试吧......") return } else { setTimeout(findVideo, 3000) } } } console.log("---------开始执行 YM Coursera 字幕处理 ") // 先注销改成 按钮触发 // findVideo() // 放置 $videoWrapper 到合适的位置 function posVideoWrapperAdd(addVal, doScrollType) { posVideoWrapper($videoWrapper.width() + addVal, doScrollType) } function posVideoWrapperReduce(addVal, doScrollType) { posVideoWrapper($videoWrapper.width() - addVal, doScrollType) } // showMode==1 浮动模式下,计算video播放窗口大小和位置 // doScrollType = 0 字幕区域不滚动到视频下方 // doScrollType = 1 字幕区域直接设置到视频下方位置,不做动画 // doScrollType = 2 字幕区域直置到视频下方位置,用动画方式滚动 function posVideoWrapper(targetWidth, doScrollType) { let $refBlock = $('.ItemLecture_Video_Title:first') if(!$refBlock.length) { $refBlock = $('.rc-VideoHighlightingManager:first') if(!$refBlock.length) { $refBlock = $('h1.video-name') if(!$refBlock.length) { console.error('courser页面结构变化很大,找不到视频参考元素in posVideoWrapper'); } } } let left = 10, top = 16, width = 0; if($refBlock.length) { left = $refBlock.offset().left; width = $refBlock.width() } if(targetWidth && targetWidth > 0) { // 如果显式指定width if(targetWidth <= width) { left = Math.floor(left + (width - targetWidth) / 2) width = targetWidth } else { if(targetWidth > 1200) { targetWidth = 1200 } left = Math.floor(left - (targetWidth - width) / 2) let $winWidth = $(window).width() if(left + targetWidth > $winWidth) { left = $winWidth - targetWidth if(left < 10) { left = 10 targetWidth = $winWidth - left } } width = targetWidth } } let posCss = { "top": top + 'px', "left": left + 'px', } if(width && width > 0) { posCss.width = width + "px" } $videoWrapper.css(posCss) // console.log('posVideoWrapper set posCss', posCss) let height = $videoWrapper.height() let $contentWrap = $('.ItemPageLayout_content_body:first') $extSubtitleWrap.css('margin-top', (height + 40 - $contentWrap.offset().top) + 'px') if(doScrollType == 2) { $contentWrap.animate({ scrollTop: 20 }, 1000); } else if(doScrollType == 1) { $contentWrap.scrollTop(20) } } /***** 这个计算方法废弃 // 计算video播放窗口大小和位置 function posVideoWrapper(targetWidth) { let RIO = 0.62 let $win = $(window),maxWidth = $win.width() - 25, maxHeight = $win.height() - 200, width, height; if(maxWidth * RIO > maxHeight) { if(maxHeight < 50) { maxHeight = Math.floor($win.height() * 0.7) } height = maxHeight width = Math.floor(height / RIO) } else { width = maxWidth height = Math.floor(width * RIO) } console.log('videoWrap重置宽高计算出来的值:', width, height) if(targetWidth && targetWidth > 0 && targetWidth < width) { width = targetWidth } let left = 5 + Math.floor(($win.width() - 25 - width) / 2) $videoWrapper.css({ "top": 20 + 'px', "left": left + 'px', "width": width + "px", // "height": height + "px", }) width = $videoWrapper.width() height = $videoWrapper.height() console.log('videoWrap重置宽高实际值:', width, height) } ***********/ // 视频浮动显示 function videoFloatShow() { // 计算video的大小 // 先把 $videoWrapper 挪到页面 absolute 流中 $videoWrapper.addClass('ym_videoFloatWrapper') $videoWrapper.css({ "position": "absolute", "z-index": 99999, }) $(document.body).append($videoWrapper.remove()) posVideoWrapper(0, 2) $(window).resize(function() { if(showMode == 1) { posVideoWrapper(0, 1) } }) let $contentWrap = $('.ItemPageLayout_content_body:first') let $videoPlayer = $('.rc-VideoMiniPlayer:first') $contentWrap.scroll(function() { // 防止出现coursera自带的右下角mini播放器效果 $videoPlayer.removeClass('mini') $videoPlayer.find('.video-placeholder').css('height', '0px') setTimeout(function() { $videoPlayer.removeClass('mini') $videoPlayer.find('.video-placeholder').css('height', '0px') }, 10) }) /*************** // **** 这段代码在把字幕区域加到文档末尾时可用, 不要删除,留着备用和参考。 // 带脚本文本的长文本滚动区域 let $contentWrap = $('.ItemPageLayout_content_body:first') let initScrollTop = $contentWrap.scrollTop() + $extSubtitleWrap.offset().top - height - 25 console.log('计算滚动区域scrollTop', $contentWrap.scrollTop(), $extSubtitleWrap.offset(), height, initScrollTop) // $contentWrap.scrollTop(initScrollTop) $contentWrap.animate({ scrollTop: initScrollTop }, 2000); // ItemLecture_Video_Highlights // $contentWrap.scroll(function() { // console.log('$contentWrap.scroll', $contentWrap.offset(), $extSubtitleWrap.offset(), $contentWrap.scrollTop()) // }) // console.log("$extSubtitleWrap.offset()", $extSubtitleWrap.offset()) *******************/ } $(function() { // document ready let $subExBtnWrap = $('#ym_subExBtnWrap') if(!$subExBtnWrap.length) { let $exSubtitleInitBtn = $(`<button class="ym_subExBtn">SubEx</button>`) $exSubtitleInitBtn.click(function() { findVideo(0) }) let $exVideoExActionBtn = $(`<button class="ym_subExBtn">VidEx</button>`) $exVideoExActionBtn.click(function() { findVideo(1) }) $subExBtnWrap = $(`<div id="ym_subExBtnWrap"></div>`).append($exSubtitleInitBtn).append($exVideoExActionBtn) $(document.body).append($subExBtnWrap) } }) }) })();