feat: 新增心理论坛功能并优化订单相关文案;更新音视频播放组件版本;

- 新增心理论坛页面及文章详情页
- 更新订单状态文案:"待发货"改为"待发出","待收货"改为"待收到"
- 统一修改"收货地址"相关文案为"收件地址"
- 添加安卓应用包下载地址配置
- 更新依赖edu-core至v1.0.6版本
- 优化首页课程列表布局和样式
- 新增分享功能到我的页面
This commit is contained in:
2026-02-28 16:32:38 +08:00
parent 0f72e08dde
commit a64d538cd3
100 changed files with 21283 additions and 5094 deletions

View File

@@ -0,0 +1,102 @@
/*手势事件*/
.yb-player-center-value {
font-size: 18px;
padding: 20px 10px;
border-radius: 5px;
background-color: rgba(0, 0, 0, 0.5);
color: #fff;
}
.yb-player-rate {
position: absolute;
top: 10%;
left: 50%;
padding: 5px 15px;
border-radius: 5px;
background-color: rgba(0, 0, 0, 0.5);
color: #fff;
font-size: 14px;
transform: translateX(-50%);
display: flex;
align-items: center;
}
.yb-player-rate-icon {
display: flex;
align-items: center;
}
.yb-player-rate-icon i {
width: 0;
height: 0;
border-left: 10px solid #fff;
border-right: 10px solid transparent;
border-bottom: 5px solid transparent;
border-top: 5px solid transparent;
animation: arrowMove 1s infinite linear;
margin-right: -5px;
}
.yb-player-rate-icon i:nth-child(0) {
animation-delay: 0s;
}
.yb-player-rate-icon i:nth-child(1) {
animation-delay: 0.3s;
}
.yb-player-rate-icon i:nth-child(2){
animation-delay: 0.5s;
}
@keyframes arrowMove {
0%, 100% {
opacity: 0.2;
}
50% {
opacity: 1;
}
}
/*字幕*/
.yb-player-subtitle-text {
position: absolute;
left: 0;
width: 100%;
box-sizing: border-box;
display: flex;
justify-content: center;
padding: 0 5%;
}
.yb-player-subtitle-loading {
position:absolute;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0,0,0,.5);
color:#fff;bottom: 50px;
font-size: 18px;
padding: 5px 20px;
}
/*弹幕过滤*/
.yb-player-filter-item {
display: flex;
align-items: center;
margin-top: 5px;
}
.yb-player-filter-item-type {
padding: 2px 5px;
background-color: var(--color-warning);
font-size: 12px;
color: #fff;
margin-right: 10px;
border-radius: 5px;
}
.yb-player-filter-item-content {
font-size: 14px;
color: #eee;
flex: 1;
}
.yb-player-filter-item button {
background-color: var(--color-error);
font-size: 14px;
color: #fff;
border: none;
}

View File

@@ -0,0 +1,823 @@
:root {
--color-primary: #007aff;
--color-success: #4cd964;
--color-warning: #f0ad4e;
--color-error: #dd524d;
--safe-area-default-gap: 15px;
--safe-area-default-top-gap: 10px;
--safe-area-default-bottom-gap: 10px;
--safe-area-inset-top: var(--safe-area-default-top-gap);
--safe-area-inset-right: var(--safe-area-default-gap);
--safe-area-inset-bottom: var(--safe-area-default-bottom-gap);
--safe-area-inset-left: var(--safe-area-default-gap);
@supports (top: constant(safe-area-inset-top)) {
--safe-area-inset-top: max(var(--safe-area-default-top-gap), constant(safe-area-inset-top));
--safe-area-inset-right: max(var(--safe-area-default-gap), constant(safe-area-inset-right));
--safe-area-inset-bottom: max(var(--safe-area-default-bottom-gap), constant(safe-area-inset-bottom));
--safe-area-inset-left: max(var(--safe-area-default-gap), constant(safe-area-inset-left));
}
@supports (top: env(safe-area-inset-top)) {
--safe-area-inset-top: max(var(--safe-area-default-top-gap), env(safe-area-inset-top));
--safe-area-inset-right: max(var(--safe-area-default-gap), env(safe-area-inset-right));
--safe-area-inset-bottom: max(var(--safe-area-default-bottom-gap), env(safe-area-inset-bottom));
--safe-area-inset-left: max(var(--safe-area-default-gap), env(safe-area-inset-left));
}
}
.yb-player-wrapper {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
box-sizing: border-box;
}
/* 模拟全屏样式 */
.yb-player-openfull {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100vw;
height: 100vh;
z-index: 9999999;
}
.yb-player-danmu {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
}
.yb-player-time {
min-width: 35px;
}
/* .yb-player-duration {
width: 35px;
} */
.yb-player-bottom-progress {
position: absolute;
bottom: 0;
left: 0;
height: 1px;
background-color: var(--color-error);
width: 0;
}
.yb-player-controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 2;
padding: 10px 15px;
transform: translateY(100%);
-webkit-transform: translateY(100%);
transition: transform .3s;
color: #fff;
font-size: 15px;
}
.yb-player-controls-show {
transform: translateY(0);
-webkit-transform: translateY(0);
}
.yb-player-controls svg {
width: 25px;
height: 25px;
fill: #fff;
}
.yb-player-progress {
display: flex;
flex-direction: row;
align-items: center;
}
.yb-player-progress > * {
margin-left: 5px;
}
.yb-player-progress > :first-child {
margin-left: 0;
}
.yb-player-progress span {
color: #fff;
font-size: 14px;
}
.yb-player-progress-center {
display: flex;
align-items: center;
flex: 1;
}
.yb-player-progress-toggle {
margin-right: 10px;
}
.yb-player-progress-fullscreen {
margin-left: 10px;
}
.yb-player-live {
margin-right: 5px;
}
.yb-player-range-box {
width: 0;
flex: 1;
position: relative;
height: 30px;
margin: 0 10px;
}
.yb-player-range-track {
position: absolute;
top: 14px;
left: 0;
right: 0;
bottom: 14px;
background-color: #999;
}
.yb-player-range-focus, .yb-player-range-preload {
position: absolute;
top: 14px;
left: 0;
bottom: 14px;
background-color: #fff;
width: 0;
}
.yb-player-range-preload {
background-color: #ccc;
}
.yb-player-range-thumb {
position: absolute;
top: 50%;
left: 0;
transform: translate(-50%, -50%);
width: 15px;
height: 15px;
border-radius: 15px;
background-color: #fff;
}
.yb-player-range:focus {
outline: none;
}
.yb-player-range {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background: none;
outline: none;
width: 100%;
height: 30px;
margin: 0;
position: absolute;
top: 0;
bottom: 0;
left: 0;
}
.yb-player-range::-webkit-slider-thumb {
-webkit-appearance: none;
height: 14px;
width: 14px;
border-radius: 14px;
background: #fff;
}
.yb-player-range::-moz-range-thumb {
height: 7px;
width: 7px;
border-radius: 8px;
background: #fff;
}
.yb-player-range::-ms-thumb {
height: 7px;
width: 7px;
border-radius: 8px;
background: #fff;
}
.yb-player-controls-bottom {
display: flex;
flex-direction: row;
align-items: center;
}
.yb-player-controls-bottom > * {
margin-left: 5px;
}
.yb-player-controls-bottom > :first-child {
margin-left: 0;
}
.yb-player-toggle {
margin-left: 0;
}
.yb-player-toggle svg {
width: 18px;
height: 18px;
}
.yb-player-next {
margin-left: 0;
}
.yb-player-danmu-send {
flex: 1;
width: 0;
box-sizing: border-box;
padding: 5px 10px;
border-radius: 5px;
background-color: #000;
color: #999;
font-size: 14px;
}
.yb-player-header {
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 10px 15px;
background-color: rgba(0,0,0,0.5);
align-items: center;
z-index: 2;
display: flex;
color: #fff;
font-size: 14px;
transform: translateY(-100%);
transition: transform .3s;
}
.yb-player-header-show {
transform: translateY(0);
}
.yb-player-title {
color: #fff;
font-size: 14px;
margin-top: -2px;
flex: 1;
word-break: break-all;
}
.yb-player-header svg {
width: 25px;
height: 25px;
fill: #fff;
}
.yb-player-header > * {
margin-left: 10px;
}
.yb-player-header > *:first-child {
margin-left: 0;
}
.yb-player-error {
display: flex;
flex-direction: column;
align-items: center;
max-width: 80%;
}
.yb-player-error-btns {
display: flex;
align-items: center;
margin-top: 10px;
}
.yb-player-error-btn {
padding: 10px 20px;
color: #fff;
font-size: 14px;
border-radius: 5px;
}
.yb-player-error-close {
background-color: var(--color-error);
}
.yb-player-error svg {
fill: #fff;
stroke: #fff;
width: 30px!important;
height: 30px!important;
}
.yb-player-error span {
font-size: 14px!important;
color: #fff;
margin-top: 5px;
}
.yb-player-toast {
position: absolute;
bottom: 10%;
left: 50%;
transform: translate(-50%, 200px);
width: 80%;
display: flex;
justify-content: center;
z-index: 4;
}
.yb-player-toast-message {
background-color: rgba(0, 0, 0, 0.5);
color: #fff;
font-size: 14px;
padding: 5px 15px;
border-radius: 5px;
}
.yb-player-toast-show {
animation: show-toast 300ms both;
}
.yb-player-toast-hide {
animation: hide-toast 300ms both;
}
.yb-player-capture-popup {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
height: 80%;
z-index: 1;
animation: capture 0.1s ease-in-out both;
box-sizing: border-box;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
background-color:#fff;
padding: 5px;
display: flex;
flex-direction: column;
}
.yb-player-capture-image {
width: 100%;
flex: 1;
height: 0;
flex-shrink: 0;
}
.yb-player-capture-btns {
display: flex;
align-items: center;
padding: 10px 0 5px 0;
}
.yb-player-capture-btn {
flex: 1;
color: #333;
text-align: center;
}
.yb-player-capture-btn:first-child {
border-right: 1px solid #999;
}
.yb-player-capture-save {
color: var(--color-error);
}
.yb-player-lock {
background-color: rgba(0, 0, 0, 0.5);
width: 50px;
height: 50px;
border-radius: 50px;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 2;
}
.yb-player-lock svg {
width: 20px;
height: 20px;
fill: #fff;
}
.yb-player-lock-left {
left: var(--safe-area-inset-left);
}
.yb-player-lock-right {
right: var(--safe-area-inset-right);
}
/* 设置弹窗 */
.yb-player-setting-line {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 16px;
box-sizing: border-box;
width: 100%;
margin-top: 15px;
color: #fff;
}
.yb-player-setting-label {
flex-shrink: 0;
margin-right: 20px;
}
.yb-player-setting-right {
display: flex;
align-items: center;
}
.yb-player-setting-mini-btn {
border: none;
background-color: #fff;
color: #333;
padding: 5px 10px;
}
.yb-player-setting-reduce {
width: 20px;
height: 3px;
background-color: #fff;
}
.yb-player-setting-add {
width: 20px;
height: 3px;
background-color: #fff;
position: relative;
}
.yb-player-setting-add::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 3px;
height: 20px;
background-color: #fff;
}
.yb-player-setting-switch {
width: 50px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
}
.yb-player-setting-switch-active {
background-color: var(--color-error);
border-radius: 30px;
}
.yb-player-setting-input {
color: #fff;
border: none;
border-bottom: 1px solid #fff;
background: none;
font-size: 14px;
flex: 1;
width: 0;
padding: 0;
margin: 0;
outline: none;
min-height: 35px;
box-sizing: border-box;
}
.yb-player-quality-box {
width: 50%;
}
.yb-player-setting-danmu-speed,
.yb-player-setting-danmu-opacity-percent,
.yb-player-setting-danmu-size,
.yb-player-setting-danmu-diffrence {
width: 100px;
text-align: center;
}
.yb-player-danmu-send-color::-webkit-color-swatch-wrapper {
padding: 0;
}
.yb-player-danmu-send-color::-webkit-color-swatch {
border: 0;
}
.yb-player-danmu-send-textarea::placeholder {
color: #999;
}
.yb-player-danmu-send-btn {
padding: 8px 20px;
text-align: center;
background-color: var(--color-error);
color: #fff;
font-size: 18px;
border-radius: 5px;
margin-top: 10px;
}
.yb-player-work-item {
padding: 10px;
border: 1px solid #fff;
color: #fff;
font-size: 14px;
margin-top: 10px;
}
.yb-player-work-item-active {
border: 1px solid var(--color-error);
background-color: var(--color-error);
}
.yb-player-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.yb-player-center svg {
width: 50px;
height: 50px;
fill: #fff;
}
.yb-player-hide {
display: none!important;
}
.yb-player-check {
width: 20px;
height: 20px;
border-radius: 20px;
border: 1px solid #fff;
padding: 1px;
box-sizing: border-box;
}
.yb-player-check-active {
background-color: var(--color-error);
position: relative;
}
.yb-player-check-active::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 40%;
height: 40%;
border-radius: 50%;
background-color: #fff;
}
.yb-player-ellipsis {
white-space: nowrap; /* 防止文本换行 */
overflow: hidden; /* 隐藏溢出的内容 */
text-overflow: ellipsis; /* 显示省略符号来代表被修剪的文本 */
}
.yb-player-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 2;
}
.yb-player-toolbar {
position: absolute;
background-color: #000;
color: #fff;
font-size: 14px;
transform-origin: top right;
animation: toolbar .05s linear both;
z-index: 2;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
overflow-y: auto;
}
.yb-player-toolbar-item {
padding: 10px 20px;
border-bottom: 1px solid #333;
display: flex;
align-items: center;
min-width: 100px;
}
.yb-player-toolbar-item span:first-child {
flex: 1;
}
.yb-player-popup {
position: absolute;
z-index: 3;
background-color: rgba(0, 0, 0, 0.5);
box-sizing: border-box;
padding: 20px;
display: flex;
flex-direction: column;
}
.yb-player-popup-title {
font-size: 16px;
color: #fff;
text-align: center;
height: 25px;
line-height: 25px;
}
.yb-player-popup-close {
position: absolute;
top: 20px;
left: 20px;
width: 25px;
height: 25px;
border-radius: 20px;
background-color: var(--color-error);
}
.yb-player-popup-close::after, .yb-player-popup-close::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 2px;
height: 16px;
background-color: #fff;
}
.yb-player-popup-close::after {
transform: translate(-50%, -50%) rotateZ(45deg);
}
.yb-player-popup-close::before {
transform: translate(-50%, -50%) rotateZ(-45deg);
}
.yb-player-popup-content {
flex: 1;
overflow-y: auto;
width: 100%;
}
.yb-player-popup-portrait {
bottom: 0;
left: 0;
right: 0;
max-height: 80%;
animation: popupBottom .2s linear both;
}
.yb-player-popup-landscape {
width: 50%;
right: 0;
top: 0;
bottom: 0;
animation: popupRight .2s linear both;
}
.yb-player-popup-center {
min-width: 50%;
max-width: 80%;
max-height: 80%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
animation: popupCenter .2s linear both;
}
.yb-player-icon {
position: relative;
font-size: 12px;
padding: 0 5px;
border-radius: 5px;
border: 1px solid #fff;
height: 20px;
line-height: 20px;
}
.yb-player-icon-close::before {
content: '';
position: absolute;
width: 105%;
height: 1px;
background-color: #fff;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotateZ(30deg);
}
.yb-player-icon svg {
width: 17px;
height: 17px;
fill: #fff;
}
.spinner {
width: 60px;
height: 60px;
animation: rotate 1.4s linear infinite;
}
.spinner circle {
stroke: #ffffff;
stroke-width: 4;
stroke-dasharray: 80, 200;
stroke-dashoffset: 0;
animation: dash 1.4s ease-in-out infinite;
stroke-linecap: round;
}
/* 安全区域 */
.yb-player-controls-safearea {
padding-right: max(var(--safe-area-inset-left), var(--safe-area-inset-right));
padding-left: max(var(--safe-area-inset-left), var(--safe-area-inset-right));
padding-bottom: var(--safe-area-inset-bottom);
}
.yb-player-header-safearea {
padding-right: max(var(--safe-area-inset-left), var(--safe-area-inset-right));
padding-left: max(var(--safe-area-inset-left), var(--safe-area-inset-right));
padding-top: var(--safe-area-inset-top);
}
.yb-player-locks-safearea .yb-player-lock-left {
left: max(var(--safe-area-inset-left), var(--safe-area-inset-right));
}
.yb-player-locks-safearea .yb-player-lock-right{
right: max(var(--safe-area-inset-left), var(--safe-area-inset-right));
}
/* 横向滚动样式 */
.horizontal-scroll {
white-space: nowrap;
overflow: hidden;
}
.horizontal-content {
display: inline-block;
padding-right: 50px;
animation: horizontal-scroll 15s linear infinite;
}
.horizontal-scroll:hover .horizontal-content {
animation-play-state: paused;
}
/* 动画定义 */
@keyframes horizontal-scroll {
0% {
transform: translateX(0%);
}
100% {
transform: translateX(-100%);
}
}
/* 动画效果 */
@keyframes rotate {
100% {
transform: rotate(360deg);
}
}
@keyframes dash {
0% {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 89, 200;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 89, 200;
stroke-dashoffset: -124;
}
}
@keyframes show-toast {
0% {
transform: translate(-50%, 200px);
}
100% {
transform: translate(-50%, 0);
}
}
@keyframes hide-toast {
0% {
transform: translate(-50%, 0);
}
100% {
transform: translate(-50%, 200px);
}
}
@keyframes capture {
0% {
transform: translate(-50%, -50%) scale(1.3);
}
100% {
transform: translate(-50%, -50%) scale(1);
}
}
@keyframes toolbar {
0% {
transform: scale(0.2);
}
100% {
transform: scale(1);
}
}
@keyframes popupBottom {
0% {
transform: translateY(100%);
}
100% {
transform: translateY(0);
}
}
@keyframes popupRight {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(0);
}
}
@keyframes popupCenter {
0% {
transform: translate(-50%, -50%) scale(0);
}
100% {
transform: translate(-50%, -50%) scale(1);
}
}

View File

@@ -0,0 +1,671 @@
"use strict";
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
var YbDanmu = /*#__PURE__*/function () {
function YbDanmu(container, data) {
var config = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
_classCallCheck(this, YbDanmu);
this.container = typeof container == 'string' ? document.querySelector(container) : container;
this.config = _objectSpread(_objectSpread({}, YbDanmu.DEFAULT_CONFIG), config);
this.data = data;
this.filter = new YbDanmuFilter(this.config.filter); //过滤规则
this.list = this._filterData(data);
this.runline = []; //正在跑动的弹幕列表
this.canvas = null; //canvas元素
this.ctx = null; //canvas实例
this.paused = true; //是否暂停
this.currentTime = 0; //当前时间轴
this._animation = null; //动画实例
}
return _createClass(YbDanmu, [{
key: "load",
value:
//加载弹幕
function load() {
if (this.container) {
var canvas = document.createElement('CANVAS');
this.container.appendChild(canvas);
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.updateSize();
}
}
//卸载弹幕
}, {
key: "unload",
value: function unload() {
this._clearAnimationFrame();
this.list = [];
this.runline = [];
if (this.ctx) this.ctx = null;
if (this.canvas) {
this.canvas.remove();
this.canvas = null;
}
}
//更新数据
}, {
key: "setData",
value: function setData(data) {
this.data = data;
this.reset();
}
//动态配置
}, {
key: "setConfig",
value: function setConfig(key, value) {
this.config[key] = value;
}
//播放弹幕
}, {
key: "play",
value: function play() {
this.paused = false;
this._render();
}
//暂停弹幕
}, {
key: "pause",
value: function pause() {
this._clearAnimationFrame();
this.paused = true;
}
//跳转弹幕
}, {
key: "seek",
value: function seek(time) {
this.currentTime = time;
this.reset();
}
//更新时间轴(需要外部更新时间轴否则弹幕不会运行)
}, {
key: "time",
value: function time(_time) {
var _this = this;
//计算本次更新时间和上次时间的时间差
var diffrenece = _time - this.currentTime;
//记录新时间
this.currentTime = _time;
//新列表
var newList = [];
//跑动列表
var runline = [];
//循环弹幕列表
this.list.forEach(function (danmu) {
//当前时间减去弹幕显示时间
var range = _this.getCurrentTime() - danmu.time;
//获取当前时间和上次时间的时间差以内的弹幕
if (range >= 0 && range < Math.abs(diffrenece)) runline.push(danmu);
//记录不在范围内的弹幕
else newList.push(danmu);
});
//去掉已经渲染的弹幕
this.list = newList;
//计算需要渲染弹幕的布局
for (var i = 0; i < runline.length; i++) {
var bar = this._layout(runline[i]);
if (bar) this.runline.push(bar);
}
//排序弹幕
this.runline.sort(function (a, b) {
return a.time - b.time;
});
}
//清空画布
}, {
key: "clear",
value: function clear() {
this.ctx && this.ctx.clearRect(0, 0, this.canvas.offsetWidth, this.canvas.offsetHeight);
}
//显示弹幕舞台
}, {
key: "show",
value: function show() {
this.container.style.visibility = 'visible';
}
//隐藏弹幕舞台
}, {
key: "hide",
value: function hide() {
this.container.style.visibility = 'hidden';
}
//更新尺寸
}, {
key: "updateSize",
value: function updateSize() {
var width = this.container.offsetWidth;
var height = this.container.offsetHeight;
var dpr = this.config.accuracy == 'auto' ? window.devicePixelRatio : this.config.accuracy;
this.canvas.width = width * dpr;
this.canvas.height = height * dpr;
this.canvas.style.width = width;
this.canvas.style.height = height;
this.ctx.scale(dpr, dpr);
}
//重置数据和画布
}, {
key: "reset",
value: function reset() {
var _this2 = this;
this.clear();
this.list = this._filterData(this.data);
this.runline = [];
var duration = this.canvas.offsetWidth / (this.config.speed * this.config.playbackRate) / 60; //滚动周期除以60是因为为大概每秒60帧根据滚动周期去获取可能出现在屏幕上的弹幕
var newList = [];
var runline = [];
this.list.forEach(function (danmu) {
var range = _this2.getCurrentTime() - danmu.time;
//重置数据的时候,考虑到播放进度可能在中间,未避免弹幕丢失,将时间误差控制在滚动周期以内
if (range >= 0 && range < duration) runline.push(danmu);
//记录不在范围内的弹幕
else newList.push(danmu);
});
//去掉已经渲染的弹幕
this.list = newList;
//计算渲染弹幕的布局
for (var i = 0; i < runline.length; i++) {
var bar = this._layout(runline[i]);
if (bar) this.runline.push(bar);
}
this.runline.sort(function (a, b) {
return a.time - b.time;
}); //排序弹幕
}
}, {
key: "refresh",
value: function refresh() {
this.updateSize();
this.reset();
}
//弹幕是否正在显示
}, {
key: "getVisible",
value: function getVisible() {
var _this$container$style;
return this.container ? ((_this$container$style = this.container.style) === null || _this$container$style === void 0 ? void 0 : _this$container$style.visibility) == 'visible' : false;
}
//获取时间进度
}, {
key: "getCurrentTime",
value: function getCurrentTime() {
return this.currentTime + this.config.timeDiffrence; //加上时间差
}
/**
* 发送弹幕
* @param {Object} danmu 弹幕数据
* @param {Boolean}border 使用边框
*/
}, {
key: "send",
value: function send(danmu, border) {
this.data.push(danmu);
this.runline.push(this._layout(_objectSpread(_objectSpread({}, danmu), {}, {
time: danmu.time + this.config.timeDiffrence
}), border, true)); //强制显示弹幕
}
//插入弹幕数据,但不会渲染
}, {
key: "insert",
value: function insert(danmu) {
this.data.push(danmu);
this.list.push(danmu);
}
//渲染弹幕
}, {
key: "_render",
value: function _render() {
var _this3 = this;
this._clearAnimationFrame();
if (this.paused) return;
if (this.runline.length) {
this.clear();
for (var i = 0; i < this.runline.length; i++) {
var b = this.runline[i];
//移除渲染完毕的弹幕
if (b.left + b.width <= 0 || b.boxWidth <= 0) {
this.runline.splice(i, 1);
i--;
continue;
}
// b.speed = this._detectionBump(b);//碰撞检测,保留位置,可能不需要
var speed = (this.config.speed + b.speed) * this.config.playbackRate; //弹幕随机的速度加上默认设置速度再乘以倍速,才是真正移动的速度
if (b.mode == 1 || !b.mode) b.left -= speed; //滚动弹幕将x轴位置减去速度实现滚动动画
else b.boxWidth -= speed; //顶端和底端弹幕,将记录的画布宽度减去速度,实现渲染时长
//非滚动弹幕当boxWidth大于画布宽度时则不绘制避免弹幕提前出现
if (b.mode == 1 || !b.mode || b.boxWidth <= this.canvas.offsetWidth) this._drawText(b);
}
}
this._animation = window.requestAnimationFrame(function () {
return _this3._render();
});
}
/**
* 计算弹幕布局
* @param { Object|String } danmu 弹幕对象
* @param {Boolean} border 是否使用边框
* @param {Boolean} force 是否强制显示
*/
}, {
key: "_layout",
value: function _layout(danmu, border, force) {
var ctx = this.ctx;
var canvas = this.canvas;
var config = this.config;
var boxWidth = canvas.offsetWidth;
var fontSize = danmu.fontSize || config.fontSize;
var fontFamily = danmu.fontFamily || config.fontFamily;
var fontScale = config.fontScale;
var color = danmu.color || config.color;
var mode = danmu.mode || 1;
var text = danmu.text;
var time = danmu.time;
ctx.font = "".concat(fontSize * fontScale, "px ").concat(fontFamily); //设置字体大小和样式
var width = Math.ceil(ctx.measureText(text).width); //测算文本宽度
var speed = mode == 1 ? Math.random() * 0.1 : 0; //随机移动速度(弹幕一帧移动的距离,加上随机数,可以让弹幕滚动更加有层次感)
var offset = (this.getCurrentTime() - time) * 60 * (config.speed + speed); //根据弹幕出现时间和当前时间轴的时间差,计算弹幕的初始偏移值
var left = mode == 1 ? boxWidth - offset : boxWidth / 2 - width / 2; //获取弹幕x轴位置
var track = this._getTrack(fontSize, mode, offset, force); //获取弹幕y轴位置
var top = track.top;
if (top > -1) {
return {
mode: mode,
text: text,
time: time,
fontSize: fontSize,
color: color,
top: top,
left: left,
speed: track.isOverlap ? speed + 2 + Math.random() * 0.5 : speed,
//如果是重叠的弹幕则加快速度,避免一直重叠,这样观感不好
width: width,
boxWidth: boxWidth - offset,
//顶部和底部弹幕的显示时间根据舞台宽度和移动速度决定
border: border //自己发送的弹幕需要加上边框,作区别显示
};
}
return false;
}
/**
* 绘制文字
* @param {Object} danmu 弹幕对象
* @param {String} mode 弹幕模式 1-滚动 4-底部 5-顶部
*/
}, {
key: "_drawText",
value: function _drawText(danmu) {
var mode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
var ctx = this.ctx;
var config = this.config;
if (ctx) {
ctx.beginPath();
var fontSize = danmu.fontSize || config.fontSize;
var fontFamily = danmu.fontFamily || config.fontFamily;
var color = danmu.color || config.color;
var opacity = config.opacity;
var fontScale = config.fontScale;
var newFontSize = fontSize * fontScale;
ctx.font = "".concat(fontSize * fontScale, "px ").concat(fontFamily);
ctx.strokeStyle = YbDanmu.colorToRgba(YbDanmu.getStrokeColor(color), opacity);
ctx.strokeText(danmu.text, danmu.left, danmu.top);
ctx.fillStyle = YbDanmu.colorToRgba(color, opacity);
ctx.fillText(danmu.text, danmu.left, danmu.top);
if (danmu.border) {
//如果带边框
ctx.strokeStyle = color; //边框颜色
ctx.lineWidth = 2; //边框宽度
ctx.strokeRect(danmu.left, danmu.top - newFontSize, danmu.width, newFontSize + config.lineGap); //绘制边框
}
ctx.closePath();
}
}
/**
* 计算弹幕应该放在哪条轨道
* @param fontSize弹幕字体大小
* @param mode弹幕模式1-滚动 4-底部 5-顶部
* @param offset弹幕初始x轴位置偏差值
* @param force是否强制显示用于弹幕发送
*/
}, {
key: "_getTrack",
value: function _getTrack(fontSize) {
var mode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
var force = arguments.length > 3 ? arguments[3] : undefined;
//canvas绘制文字x,y坐标是按文字左下角计算预留30px
var canvas = this.canvas;
var isOverlap = false; //是否重叠
var nowTop = -1; //最终返回的顶部距离
var minLen = -1; //最小弹幕数量
var minTop = -1; //最小弹幕数量轨道
var boxWidth = canvas.offsetWidth;
var boxHeight = canvas.offsetHeight;
var config = this.config;
var lineGap = config.lineGap;
var top = config.top;
var bottom = config.bottom;
var fontScale = config.fontScale;
var newFontSize = fontSize * fontScale;
var reserve = 5; //画布预留位置(给画布底部流出一段距离)不预留可能导致最后一个轨道的弹幕显示不全
var trackLen = Math.floor((boxHeight - top - bottom) / (newFontSize + lineGap)); //计算轨道数量
var tracks = [];
if (mode == 4) {
//弹幕模式为底端,从底部开始加载,所以要用画布高度减去轨道高度,才是底端弹幕渲染的轨道高度
for (var i = 0; i < trackLen; i++) tracks.push(boxHeight - (i * newFontSize + i * lineGap + top + reserve)); //排列轨道
} else {
for (var _i = 0; _i < trackLen; _i++) tracks.push((_i + 1) * newFontSize + _i * lineGap + top); //排列轨道
}
for (var _i2 = 0; _i2 < tracks.length; _i2++) {
var trackTop = tracks[_i2];
//当前轨道上有多少条弹幕
var danmus = this.runline.filter(function (danmu) {
return danmu.top < trackTop + newFontSize && danmu.fontSize * fontScale + danmu.top > trackTop && danmu.mode == mode;
});
if (danmus.length < minLen || minLen == -1) {
//获取最少弹幕数量的轨道
minLen = danmus.length;
minTop = trackTop;
}
if (danmus.length == 0) {
//当前轨道没有弹幕运行,直接插入该轨道
nowTop = trackTop;
break;
} else {
if (danmus.length > 0 && mode == 1) {
//当前轨道有弹幕运行,且弹幕模式是滚动模式时
var arr = danmus.map(function (danmu) {
return danmu.left + danmu.width;
});
var max = Math.max.apply(Math, _toConsumableArray(arr)); //获取当前轨道最右的弹幕
//如果当前轨道还有空位则将弹幕放入当前轨道
if (max < boxWidth - offset - 10) {
//预留10像素间隔
nowTop = trackTop;
break;
}
}
}
}
if (nowTop == -1 && (!config.overlap || force)) {
//没有找到可插入的轨道,但没有开启防重叠或者需要强制显示弹幕,这个弹幕弹幕会无视原来的弹幕开启新的弹幕墙
nowTop = minTop; //将弹幕强制插入最少弹幕数量的轨道
isOverlap = true; //重叠弹幕
}
return {
top: nowTop + top,
//加上上边距top
isOverlap: isOverlap
};
}
//过滤数据
}, {
key: "_filterData",
value: function _filterData(data) {
var _this4 = this;
var config = this.config;
var disableScroll = config.disableScroll;
var disableTop = config.disableTop;
var disableBottom = config.disableBottom;
return YbPlayer.deepClone(data.filter(function (item) {
return (item.mode != 1 || !disableScroll) && (item.mode != 5 || !disableTop) && (item.mode != 4 || !disableBottom) && !_this4.filter.filter(item.text);
}));
}
}, {
key: "_clearAnimationFrame",
value: function _clearAnimationFrame() {
if (this._animation) {
window.cancelAnimationFrame(this._animation);
this._animation = null;
}
}
}], [{
key: "parseBiliXml",
value:
//格式化b站XML文件弹幕
function parseBiliXml(content) {
var pattern = /<d\b[^>]*>(.*?)<\/d\s*>/g;
var danmuku = [];
var match = '';
while ((match = pattern.exec(content)) !== null) {
var attr = match[0].match(/p=\"*([\s\S]*?)\"/)[1];
var attrs = attr.split(',');
danmuku.push({
mode: attrs[1],
time: Number(attrs[0]),
color: YbDanmu.decimalToRgb(Number(attrs[3])),
fontSize: attrs[2],
text: match[1]
});
}
return danmuku;
}
//格式化b站ssa文件弹幕
}, {
key: "parseBiliSsa",
value: function parseBiliSsa(content) {
var lines = content.split('\n');
var events = [];
var currentSection;
lines.forEach(function (line) {
line = line.trim();
if (line.startsWith('[') && line.endsWith(']')) {
currentSection = line.slice(1, -1).toLowerCase();
return;
}
if (currentSection === 'events') {
if (line.toLowerCase().startsWith('format:')) {
// 解析事件格式行
var format = line.slice(7).split(',').map(function (item) {
return item.trim().toLowerCase();
});
// 存储事件格式定义
} else if (line.toLowerCase().startsWith('dialogue:')) {
// 解析对话行
var dialogueValues = line.slice(10).split(',').map(function (item) {
return item.trim();
});
// 注意:文本部分可能包含逗号,所以不能简单分割
var textIndex = 9; // 根据format文本通常是第9个字段从0开始计数
var text = dialogueValues.slice(9).join(','); // 合并文本部分
var dialogueData = {
start: dialogueValues[1],
end: dialogueValues[2],
style: dialogueValues[3],
text: text
};
events.push(dialogueData);
}
}
});
return events.filter(function (e) {
return !e.text.includes('\\a5\\pos');
}).map(function (e) {
var style = e.text.match(/\{*([\s\S]*?)\}/)[1];
var arr = style.split('\\');
return {
mode: 1,
time: timeToSeconds(e.start),
color: arr[2].replace('c&H', '#'),
fontSize: arr[3].replace('fs', ''),
text: e.text.replace(/\{.*?\}/g, '')
};
});
}
//根据文字颜色自动生成反色边框
}, {
key: "getStrokeColor",
value: function getStrokeColor(color) {
var r, g, b;
if (color.startsWith('#')) {
color = color.length == 7 ? color : '#' + color.slice(1, 4) + color.slice(1, 4);
r = parseInt(color.slice(1, 3), 16);
g = parseInt(color.slice(3, 5), 16);
b = parseInt(color.slice(5, 7), 16);
}
if (color.startsWith('rgb')) {
var match = color.match(/rgb\(*([\s\S]*?)\)/);
var colors = match[1].split(',');
r = colors[0];
g = colors[1];
b = colors[2];
}
var $grayLevel = r * 0.299 + g * 0.587 + b * 0.144 / 255;
//判断是否是深色
if ($grayLevel < 0.5) return '#ffffff';else return '#000000';
}
}, {
key: "colorToRgba",
value: function colorToRgba(color, opacity) {
if (color.startsWith('#')) {
color = color.length == 7 ? color : '#' + color.slice(1, 4) + color.slice(1, 4);
var _str = "rgba(";
var _r = parseInt(color.slice(1, 3), 16).toString();
var _g = parseInt(color.slice(3, 5), 16).toString();
var _b = parseInt(color.slice(5, 7), 16).toString();
_str += _r + "," + _g + "," + _b + "," + opacity + ")";
return _str;
}
if (color.startsWith('rgb')) {
var str = "rgba(";
var match = color.match(/rgb\(*([\s\S]*?)\)/);
var colors = match[1].split(',');
var r = colors[0];
var g = colors[1];
var b = colors[2];
str += r + "," + g + "," + b + "," + opacity + ")";
return str;
}
}
//16进制转rgb
}, {
key: "decimalToRgb",
value: function decimalToRgb(decimal) {
var r = decimal >> 16 & 255;
var g = decimal >> 8 & 255;
var b = decimal & 255;
return 'rgb(' + r + ',' + g + ',' + b + ')';
}
}]);
}(); //弹幕过滤
_defineProperty(YbDanmu, "DEFAULT_CONFIG", {
accuracy: 1,
//绘制精度 auto-自动使用屏幕DPI 任意整数-自定义精度 高精度会导致性能问题,谨慎使用
speed: 1,
//弹幕运行速度
playbackRate: 1,
//弹幕倍速
color: '#FFFFFF',
//弹幕默认颜色
fontSize: 18,
//弹幕默认大小
fontScale: 1,
//弹幕规格(多少倍大小)
fontFamily: 'Microsoft Yahei',
//弹幕默认字体
opacity: 1,
//弹幕透明度
top: 0,
//舞台顶部间距
bottom: 0,
//舞台底部间距
lineGap: 5,
//弹幕行间距
overlap: false,
//开启防重叠
timeDiffrence: 0,
//时间差(用于校准弹幕和视频进度的时间差,一般不需要)
disableScroll: false,
//关闭滚动弹幕
disableTop: false,
//关闭顶端弹幕
disableBottom: false,
//关闭底端弹幕
disableFilter: false,
//关闭弹幕过滤
filter: [] //过滤列表
});
var YbDanmuFilter = /*#__PURE__*/function () {
function YbDanmuFilter(list) {
_classCallCheck(this, YbDanmuFilter);
this.rules = list || [];
}
// 添加
return _createClass(YbDanmuFilter, [{
key: "add",
value: function add(rule) {
//判断是否存在同类型的相同规则
var index = this.rules.findIndex(function (r) {
return r.pattern == rule.pattern && r.type == rule.type;
});
if (index > -1) return false; //如果已经存在返回false表示插入失败
this.rules.push(rule);
return true; //添加成功返回true
}
// 删除
}, {
key: "remove",
value: function remove(index) {
this.rules.splice(index, 1);
}
// 过滤弹幕
}, {
key: "filter",
value: function filter(danmu) {
var _iterator = _createForOfIteratorHelper(this.rules),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var rule = _step.value;
if (rule.type === 'string') {
// 字符串匹配
if (danmu.includes(rule.pattern)) {
return true;
}
} else if (rule.type === 'regex') {
// 正则表达式匹配
try {
var regex = new RegExp(rule.pattern);
if (regex.test(danmu)) {
return true;
}
} catch (e) {
console.error('无效的正则表达式:', rule.pattern, e);
}
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
return false;
}
// 获取所有规则
}, {
key: "getRules",
value: function getRules() {
return this.rules;
}
}]);
}(); //兼容new Function为了挂载到window对象上
if (typeof window != 'undefined') {
window.YbDanmu = YbDanmu;
}

View File

@@ -0,0 +1,454 @@
"use strict";
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
/*手势事件处理*/
var YbGesture = /*#__PURE__*/function () {
function YbGesture(player, config) {
_classCallCheck(this, YbGesture);
this.player = player;
this.config = config;
this.disabled = false; //禁用手势事件
this._touchstartX = null;
this._touchstartY = null;
this._touchmoveX = null;
this._touchmoveY = null;
this._clickTime = null;
this._touchTime = null;
this._threshold = 20;
this._singleTouchTime = 150; //单击间隔时间
this._longTouchTime = 500; //长按间隔时间
this._mousedown = null;
this._isMove = null; //是否滑动
this._isVolume = null; //是否音量调节
this._isLight = null; //是否亮度调节
this._isProgress = null; //是否进度调节
this._seekTime = null; //跳转时间
this._seekVolume = null; //改变音量
this._seekLight = null; //改变亮度
this._touchTimer = null; //触摸定时器
this._longTimer = null; //长按定时器
this._centerTimer = null; //中间元素隐藏定时器
}
return _createClass(YbGesture, [{
key: "resetTouch",
value: function resetTouch() {
this._touchstartX = null;
this._touchstartY = null;
this._touchmoveX = null;
this._touchmoveY = null;
this._touchTime = null;
this._clickTime = null;
this._isMove = null;
this._isVolume = null;
this._isLight = null;
this._isProgress = null;
this._seekTime = null;
this._seekVolume = null;
this._seekLight = null;
}
}, {
key: "touchstart",
value: function touchstart(e) {
var _this = this;
if (this.stopPropagation(e)) return;
this._clearTouchTimer();
this._clearLongTimer();
var touch = e.touches[0];
this._touchstartX = touch.pageX;
this._touchstartY = touch.pageY;
this._clickTime++;
if (this._clickTime == 1) {
//第一次点击
this._touchTimer = window.setTimeout(function () {
_this._touchTime = _this._singleTouchTime;
}, this._singleTouchTime);
this._longTimer = window.setTimeout(function () {
if (_this._touchmoveX <= _this._threshold && _this._touchmoveY <= _this._threshold && !_this.config.disableLongPress) {
//当滑动距离不超过阙值时,且为禁止长按,才会执行长按
_this._touchTime = _this._longTouchTime;
_this.longPress(); //长按事件
}
}, this._longTouchTime);
}
}
}, {
key: "touchmove",
value: function touchmove(e) {
if (this.stopPropagation(e)) return;
if (this._clickTime > 0) {
var touch = e.touches[0];
this._touchmoveX = Math.abs(touch.pageX - this._touchstartX);
this._touchmoveY = Math.abs(touch.pageY - this._touchstartY);
//判断用户是否进行滑动,只要判断一次滑动,当前触摸事件就一直是滑动
if (!this._isMove) this._isMove = this._touchmoveY > this._threshold || this._touchmoveX > this._threshold;
//第二次点击 未开启全屏 未开启非全拼手势操作 锁屏中 滑动距离不大于阙值 触摸时间超过长按时间
if (this._clickTime == 2 || !this._isMove || this._touchTime == this._longTouchTime || !this.player.getFullscreen() && this.config.disableUnFullscreenEvent || this.player.disabled || this.disabled) return;
//根据视频方向,反转触摸位置和滑动距离
var container = this.player.container;
var containerWidth = container.offsetWidth;
var containerHeight = container.offsetHeight;
var touchX = this._touchstartX;
var touchY = this._touchstartY;
var offsetX = this._touchmoveX;
var offsetY = this._touchmoveY;
//点击在左边并且y轴滑动距离比x轴滑动距离大的时候执行调节亮度操作
if (touchX < containerWidth / 2 && offsetY > offsetX && !this._isVolume && !this._isProgress && !this.config.disableLight) {
this._isLight = true;
var initial = this.config.initialLight;
var max = 1;
var deltaY = touch.pageY - this._touchstartY;
// 计算滑动距离与容器宽度的比例
var percentDelta = offsetY / containerHeight;
// 根据比例和滑动可控制最大时长计算时间变化量
var delta = percentDelta * max;
// 计算新的时间位置
this._seekLight = deltaY < 0 ? initial + delta : initial - delta;
this._seekLight = this._seekLight > max ? max : this._seekLight < 0 ? 0 : this._seekLight;
this.setLight(this._seekLight);
}
//点击在右边并且y轴滑动距离比x轴滑动距离大的时候执行调节音量操作
if (touchX > containerWidth / 2 && offsetY > offsetX && !this._isLight && !this._isProgress && !this.config.disableVolume) {
this._isVolume = true;
var initial = this.config.initialVolume;
var max = 1;
var deltaY = touch.pageY - this._touchstartY;
// 计算滑动距离与容器宽度的比例
var percentDelta = offsetY / containerHeight;
// 根据比例和滑动可控制最大时长计算时间变化量
var delta = percentDelta * max;
// 计算新的时间位置
this._seekVolume = deltaY < 0 ? initial + delta : initial - delta;
this._seekVolume = this._seekVolume > max ? max : this._seekVolume < 0 ? 0 : this._seekVolume;
this.setVolume(this._seekVolume);
}
//y轴滑动距离比x轴滑动距离小的时候执行进度调节 不能为直播源 视频必须加载到数据
var video = this.player.video;
var duration = this.player.getDuration();
if (offsetY < offsetX && !this._isLight && !this._isVolume && !this.player.isLive && duration && !this.config.disableProgress) {
this._isProgress = true;
var deltaX = touch.pageX - this._touchstartX;
// 计算滑动距离与容器宽度的比例
var percentDelta = offsetX / containerWidth;
// 根据比例和滑动可控制最大时长计算时间变化量
var timeDelta = percentDelta * Math.min(180, duration);
// 计算新的时间位置
this._seekTime = deltaX > 0 ? video.currentTime + timeDelta : video.currentTime - timeDelta;
this._seekTime = this._seekTime > duration ? duration : this._seekTime < 0 ? 0 : this._seekTime;
this.setProgress(this._seekTime);
}
}
}
}, {
key: "touchend",
value: function touchend(e) {
var _this2 = this;
if (this.stopPropagation(e)) return;
this._clearTouchTimer();
this._clearLongTimer();
//当触摸事件等于
if (this._touchTime == this._longTouchTime) this.stopLongPress(); //停止长按
//点击次数大于零并且触摸时间小于单击时间并且滑动距离不超过阙值
else if (this._clickTime > 0 && this._touchTime < this._singleTouchTime && this._touchmoveX <= this._threshold && this._touchmoveY <= this._threshold) {
if (this._clickTime == 1 && !this.config.disableSingleClick) {
//第一次点击
this._touchTimer = window.setTimeout(function () {
_this2.singleClick();
}, this._singleTouchTime);
} else if (this._clickTime == 2 && !this.config.disableDoubleClick) this.doubleClick(); //第二次点击
else this.resetTouch();
} else {
var video = this.player.video;
if (this._isProgress && this._seekTime >= 0 && video) video.currentTime = this._seekTime;
if (this._isLight && this._seekLight >= 0) {
this.config.initialLight = this._seekLight; //更新初始化亮度,注意这并不代表系统亮度被改变
var data = {
seekLight: this._seekLight
};
this.player.emit('seeklight', this._seekLight);
}
if (this._isVolume && this._seekVolume >= 0) {
//更新初始化音量,注意这并不代表系统音量被改变
this.config.initialVolume = this._seekVolume;
var data = {
seekVolume: this._seekVolume
};
this.player.emit('seekvolume', this._seekVolume);
}
this.resetTouch();
}
}
}, {
key: "singleClick",
value: function singleClick() {
//单击事件
this.resetTouch();
if (this.player.controls) {
if (this.player.getControls()) this.player.hideControls();else this.player.showControls();
}
this.player.emit('singleclick');
}
}, {
key: "doubleClick",
value: function doubleClick() {
//双击事件
this.resetTouch();
this.player.toggle();
this.player.emit('doubleclick');
}
}, {
key: "longPress",
value: function longPress() {
//长按事件
this.setPlaybackRate(2.0);
this.player.emit('longpress');
}
}, {
key: "stopLongPress",
value: function stopLongPress() {
//停止长按事件
this.resetTouch();
this.setPlaybackRate(1.0);
this.player.emit('stoplongpress');
}
//注册手势事件
}, {
key: "load",
value: function load() {
var _this3 = this;
var container = this.player.container;
var wrapperEl = container.getElementsByClassName('yb-player-wrapper')[0];
//绑定触摸监听
wrapperEl.ontouchstart = function (e) {
_this3.touchstart(e);
};
wrapperEl.ontouchmove = function (e) {
_this3.touchmove(e);
};
wrapperEl.ontouchend = function (e) {
_this3.touchend(e);
};
wrapperEl.ontouchcancel = function (e) {
_this3.touchend(e);
};
//电脑端模拟触摸
wrapperEl.onmousedown = function (e) {
if ('ontouchstart' in window || window.DocumentTouch && document instanceof DocumentTouch) return; //设备支持触屏则不触发mousedown
_this3._mousedown = true;
_this3.touchstart({
target: e.target,
touches: [{
pageX: e.pageX,
pageY: e.pageY
}]
});
};
wrapperEl.onmousemove = function (e) {
if (!_this3._mousedown) return;
_this3.touchmove({
target: e.target,
touches: [{
pageX: e.pageX,
pageY: e.pageY
}]
});
};
wrapperEl.onmouseup = function (e) {
if (!_this3._mousedown) return;
_this3._mousedown = false;
_this3.touchend(e);
};
}
//注销手势事件
}, {
key: "unload",
value: function unload() {
this._clearTouchTimer();
this._clearLongTimer();
this._clearCenterTimer();
var container = this.player.container;
var wrapperEl = container.getElementsByClassName('yb-player-wrapper')[0];
if (wrapperEl) {
wrapperEl.ontouchstart = null;
wrapperEl.ontouchmove = null;
wrapperEl.ontouchend = null;
wrapperEl.onmousedown = null;
wrapperEl.onmousemove = null;
wrapperEl.onmouseup = null;
wrapperEl = null;
}
}
}, {
key: "setConfig",
value: function setConfig(key, value) {
this.config[key] = value;
}
}, {
key: "disable",
value: function disable() {
this.disabled = true;
}
}, {
key: "enable",
value: function enable() {
this.disabled = false;
}
//设置倍速
}, {
key: "setPlaybackRate",
value: function setPlaybackRate(playbackRate) {
var video = this.player.video;
if (!video) return;
if (playbackRate == video.playbackRate) return;
video.playbackRate = playbackRate;
// if ( ![1, 1.0].includes(playbackRate) ) {
// var rateEl = document.createElement('DIV')
// rateEl.innerHTML = `
// <div class="yb-player-rate">
// <div class="yb-player-rate-icon">
// <i></i><i></i><i></i>
// </div>
// <span class="yb-player-rate-span">${playbackRate + '倍速播放中'}</span>
// </div>
// `
// var wrapperEl = this.player.container.getElementsByClassName('yb-player-wrapper')[0]
// if ( wrapperEl ) wrapperEl.appendChild(rateEl)
// } else {
// var rateEl = this.player.container.getElementsByClassName('yb-player-rate')[0]
// if ( rateEl )rateEl.remove()
// }
}
//设置音量
}, {
key: "setVolume",
value: function setVolume(volume) {
var _this4 = this;
this._clearCenterTimer();
var container = this.player.container;
var wrapperEl = container.getElementsByClassName('yb-player-wrapper')[0];
var div = container.getElementsByClassName('yb-player-center-value')[0];
if (!div) {
div = document.createElement('DIV');
div.setAttribute('class', 'yb-player-center yb-player-center-value');
wrapperEl.appendChild(div);
}
div.innerHTML = "\n\t\t\t<span>\u97F3\u91CF</span>\n\t\t\t<span>".concat((volume / 1 * 100).toFixed(0), "%</span>\n\t\t");
this._centerTimer = window.setTimeout(function () {
_this4.hideCenterValue();
}, 1000);
}
//设置亮度
}, {
key: "setLight",
value: function setLight(light) {
var _this5 = this;
this._clearCenterTimer();
var container = this.player.container;
var wrapperEl = container.getElementsByClassName('yb-player-wrapper')[0];
var div = container.getElementsByClassName('yb-player-center-value')[0];
if (!div) {
div = document.createElement('DIV');
div.setAttribute('class', 'yb-player-center yb-player-center-value');
wrapperEl.appendChild(div);
}
div.innerHTML = "\n\t\t\t<span>\u4EAE\u5EA6</span>\n\t\t\t<span>".concat((light / 1 * 100).toFixed(0), "%</span>\n\t\t");
this._centerTimer = window.setTimeout(function () {
_this5.hideCenterValue();
}, 1000);
}
//设置进度
}, {
key: "setProgress",
value: function setProgress(time) {
var _this6 = this;
this._clearCenterTimer();
var container = this.player.container;
var video = this.player.video;
var wrapperEl = container.getElementsByClassName('yb-player-wrapper')[0];
var div = container.getElementsByClassName('yb-player-center-value')[0];
if (!div) {
div = document.createElement('DIV');
div.setAttribute('class', 'yb-player-center yb-player-center-value');
wrapperEl.appendChild(div);
}
div.innerHTML = "\n\t\t\t".concat(YbPlayer.timeFormat(time), " / ").concat(YbPlayer.timeFormat(this.player.getDuration()), "\n\t\t");
this._centerTimer = window.setTimeout(function () {
_this6.hideCenterValue();
}, 1000);
}
//删除手势事件产生的中间控件
}, {
key: "hideCenterValue",
value: function hideCenterValue() {
var div = this.player.container.getElementsByClassName('yb-player-center-value')[0];
if (div) {
div.remove();
div = null;
}
}
/**
* 阻止冒泡
* @param e 点击实例
*/
}, {
key: "stopPropagation",
value: function stopPropagation(e) {
var container = this.player.container;
var gestureEls = container.getElementsByClassName('yb-player-gesture'); //允许手势事件冒泡元素标记
var isGesture = false; //是否点击了非手势控制标记的元素
for (var i = 0; i < gestureEls.length; i++) {
isGesture = gestureEls[i].contains(e.target); //对比元素
if (isGesture) break; //如果找到了,打断循环
}
if (isGesture) return false; //允许手势事件
var danmuEl = container.getElementsByClassName('yb-player-danmu')[0];
var subtitleEl = container.getElementsByClassName('yb-player-subtitle')[0];
var panoEl = container.getElementsByClassName('yb-player-pano')[0];
var video = this.player.video;
// console.log('target', e.target);
// console.log('video', video.contains(e.target));
// console.log('danmuEl', danmuEl.contains(e.target));
// console.log('subtitleEl', subtitleEl.contains(e.target));
if (video && video.contains(e.target) || danmuEl && danmuEl.contains(e.target) || subtitleEl && subtitleEl.contains(e.target) || panoEl && panoEl.contains(e.target)) return false;
return true;
}
/***** 清除定时器 *****/
}, {
key: "_clearTouchTimer",
value: function _clearTouchTimer() {
if (this._touchTimer) {
window.clearTimeout(this._touchTimer);
this._touchTimer = null;
}
}
}, {
key: "_clearLongTimer",
value: function _clearLongTimer() {
if (this._longTimer) {
window.clearTimeout(this._longTimer);
this._longTimer = null;
}
}
}, {
key: "_clearCenterTimer",
value: function _clearCenterTimer() {
if (this._centerTimer) {
window.clearTimeout(this._centerTimer);
this._centerTimer = null;
}
}
}]);
}(); //兼容new Function为了挂载到window对象上
if (typeof window != 'undefined') {
window.YbGesture = YbGesture;
}

View File

@@ -0,0 +1,406 @@
"use strict";
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
var YbMpeg = /*#__PURE__*/function () {
function YbMpeg() {
_classCallCheck(this, YbMpeg);
this.canvas = null;
this._src = null; //播放链接
this.duration = null; //总时长
this.jsmpeg = null; //jsmpeg实例
this.readyState = 0; //视频状态
this._event = {}; //事件对象
this._init = null; //是否已经初始化
this._isSeeking = false; //是否正在跳转
this._seekingTime = null; //正在跳转的时间
this.init();
}
//操作播放链接
return _createClass(YbMpeg, [{
key: "src",
get: function get() {
return this._src;
},
set: function set(value) {
this._src = value;
}
//操作静音
}, {
key: "muted",
get: function get() {
var _this$jsmpeg;
return ((_this$jsmpeg = this.jsmpeg) === null || _this$jsmpeg === void 0 ? void 0 : _this$jsmpeg.volume) == 0 ? true : false;
},
set: function set(value) {
if (this.jsmpeg) {
this.jsmpeg.volume = value ? 0 : 1;
this.emit('volumechange');
}
}
}, {
key: "style",
get: function get() {
return this.canvas.style;
}
//操作进度
}, {
key: "currentTime",
get: function get() {
var _this$jsmpeg2;
return (_this$jsmpeg2 = this.jsmpeg) === null || _this$jsmpeg2 === void 0 ? void 0 : _this$jsmpeg2.currentTime;
}
//设置进度
,
set: function set(value) {
if (this.jsmpeg) {
this.jsmpeg.currentTime = value;
this._isSeeking = true;
this._seekingTime = value;
this.emit('seeking');
}
}
//获取视频宽度
}, {
key: "videoWidth",
get: function get() {
var _this$jsmpeg3;
return (_this$jsmpeg3 = this.jsmpeg) === null || _this$jsmpeg3 === void 0 || (_this$jsmpeg3 = _this$jsmpeg3.video) === null || _this$jsmpeg3 === void 0 || (_this$jsmpeg3 = _this$jsmpeg3.destination) === null || _this$jsmpeg3 === void 0 ? void 0 : _this$jsmpeg3.width;
}
//获取视频高度
}, {
key: "videoHeight",
get: function get() {
var _this$jsmpeg4;
return (_this$jsmpeg4 = this.jsmpeg) === null || _this$jsmpeg4 === void 0 || (_this$jsmpeg4 = _this$jsmpeg4.video) === null || _this$jsmpeg4 === void 0 || (_this$jsmpeg4 = _this$jsmpeg4.destination) === null || _this$jsmpeg4 === void 0 ? void 0 : _this$jsmpeg4.height;
}
//获取暂停状态
}, {
key: "paused",
get: function get() {
var _this$jsmpeg5;
return (_this$jsmpeg5 = this.jsmpeg) === null || _this$jsmpeg5 === void 0 ? void 0 : _this$jsmpeg5.paused;
}
//播放事件
}, {
key: "onplay",
get: function get() {
return this._event.onplay || null;
},
set: function set(callback) {
this._event.onplay = callback;
}
//暂停事件
}, {
key: "onpause",
get: function get() {
return this._event.onpause || null;
},
set: function set(callback) {
this._event.onpause = callback;
}
//播放结束事件
}, {
key: "onended",
get: function get() {
return this._event.onended || null;
},
set: function set(callback) {
this._event.onended = callback;
}
//数据不足等待加载事件
}, {
key: "onwaiting",
get: function get() {
return this._event.onwaiting || null;
},
set: function set(callback) {
this._event.onwaiting = callback;
}
//恢复播放事件
}, {
key: "onplaying",
get: function get() {
return this._event.onplaying || null;
},
set: function set(callback) {
this._event.onplaying = callback;
}
//开始加载事件
}, {
key: "onloadstart",
get: function get() {
return this._event.onloadstart || null;
},
set: function set(callback) {
this._event.onloadstart = callback;
}
//加载到元数据事件
}, {
key: "onloadedmetadata",
get: function get() {
return this._event.onloadedmetadata || null;
},
set: function set(callback) {
this._event.onloadedmetadata = callback;
}
//加载到第一帧事件
}, {
key: "onloadeddata",
get: function get() {
return this._event.onloadeddata || null;
},
set: function set(callback) {
this._event.onloadeddata = callback;
}
//可以播放事件
}, {
key: "oncanplay",
get: function get() {
return this._event.oncanplay || null;
},
set: function set(callback) {
this._event.oncanplay = callback;
}
//加载全部数据事件
}, {
key: "oncanplaythrough",
get: function get() {
return this._event.oncanplaythrough || null;
},
set: function set(callback) {
this._event.oncanplaythrough = callback;
}
}, {
key: "oncontextmenu",
get: function get() {
var _this$canvas;
return ((_this$canvas = this.canvas) === null || _this$canvas === void 0 ? void 0 : _this$canvas.oncontextmenu) || null;
},
set: function set(callback) {
if (this.canvas) this.canvas.oncontextmenu = callback;
}
//音量改变事件
}, {
key: "onvolumechange",
get: function get() {
return this._event.onvolumechange || null;
},
set: function set(callback) {
this._event.onvolumechange = callback;
}
//总时长改变事件
}, {
key: "ondurationchange",
get: function get() {
return this._event.ondurationchange || null;
},
set: function set(callback) {
this._event.ondurationchange = callback;
}
//事件进度更新事件
}, {
key: "ontimeupdate",
get: function get() {
return this._event.ontimeupdate || null;
},
set: function set(callback) {
this._event.ontimeupdate = callback;
}
//跳转中事件
}, {
key: "onseeking",
get: function get() {
return this._event.onseeking || null;
},
set: function set(callback) {
this._event.onseeking = callback;
}
//跳转完成事件
}, {
key: "onseeked",
get: function get() {
return this._event.onseeked || null;
},
set: function set(callback) {
this._event.onseeked = callback;
}
//中断事件
}, {
key: "onabort",
get: function get() {
return this._event.onabort || null;
},
set: function set(callback) {
this._event.onabort = callback;
}
}, {
key: "init",
value: function init() {
this.canvas = document.createElement('CANVAS');
}
//设置属性
}, {
key: "setAttribute",
value: function setAttribute(attr, value) {
this.canvas.setAttribute(attr, value);
}
//移除属性
}, {
key: "removeAttribute",
value: function removeAttribute(attr) {
//如果移除的属性是src则清空src
if (attr == 'src') {
this._src = null;
} else {
this.canvas.removeAttribute(attr);
}
}
//是否包含某个节点
}, {
key: "contains",
value: function contains(target) {
return this.canvas.contains(target);
}
//配置文件
}, {
key: "setConfig",
value: function setConfig(config) {
this.config = config;
}
}, {
key: "setDecoder",
value: function setDecoder(decoder) {
this.decoder = decoder;
}
//加载视频
}, {
key: "load",
value: function load() {
var _this = this;
this.unload();
if (!this._src) return;
this.jsmpeg = new this.decoder.Player(this._src, _objectSpread(_objectSpread({
canvas: this.canvas
}, this.config), {}, {
//当源接收到所有数据时调用的回调
onSourceCompleted: function onSourceCompleted() {
_this.readyState = 4;
_this.emit('canplaythrough');
},
//当source第一次接收到数据时调用的回调
onSourceEstablished: function onSourceEstablished() {
_this.readyState = 1;
_this.emit('canplay');
_this.emit('loadedmetadata');
},
//已连接
onSourceConnected: function onSourceConnected() {
_this.emit('loadstart');
},
//视频流解码完毕
onVideoDecode: function onVideoDecode(decoder, time) {
//还未初始化
if (!_this._init) {
//更新readyState状态
_this.readyState = 3;
_this.emit('loadeddata');
_this._init = true;
} else {
//如果刚才处于卡顿中
if (_this.readyState == 2) {
_this.readyState = 3; //更改视频状态
_this.emit('playing'); //触发playing
}
//处于跳转当中,并且当前时间和跳转时间相近,则认为跳转完成
if (_this._isSeeking && Math.abs(_this.jsmpeg.currentTime - _this._seekingTime) < 0.1) {
_this._isSeeking = false;
_this._seekingTime = null;
_this.emit('seeked');
}
_this.emit('timeupdate');
}
if (_this.jsmpeg.currentTime > _this.duration) _this.duration = _this.jsmpeg.currentTime + 1; //直播流/实时流是没有总播放时长的但为了方便外部调用duration不会报错这里默认给duration实时更新
_this.emit('durationchange');
},
//连接断开
onSourceDisconnected: function onSourceDisconnected() {
_this.readyState = 0;
_this.emit('abort');
},
//当播放开始时调用的回调函数
onPlay: function onPlay() {
_this.emit('play');
},
//当播放暂停时调用的回调函数(例如当.pause()被调用或源结束时)
onPause: function onPause() {
_this.emit('pause');
},
//当播放到达源的末尾时调用(只在loop=false调用)
onEnded: function onEnded() {
_this.emit('ended');
},
//当没有足够的数据播放时调用的回调
onStalled: function onStalled() {
_this.readyState = 2;
_this.emit('waiting');
}
}));
this.jsmpeg.volume = this.config.muted ? 0 : 1;
}
//卸载视频
}, {
key: "unload",
value: function unload() {
var _this$jsmpeg6;
(_this$jsmpeg6 = this.jsmpeg) === null || _this$jsmpeg6 === void 0 || _this$jsmpeg6.destroy();
this.jsmpeg = null;
this.duration = null;
this.jsmpeg = null;
this.readyState = 0;
this._init = null;
}
//播放视频
}, {
key: "play",
value: function play() {
var _this$jsmpeg7;
(_this$jsmpeg7 = this.jsmpeg) === null || _this$jsmpeg7 === void 0 || _this$jsmpeg7.play();
}
//暂停视频
}, {
key: "pause",
value: function pause() {
var _this$jsmpeg8;
(_this$jsmpeg8 = this.jsmpeg) === null || _this$jsmpeg8 === void 0 || _this$jsmpeg8.pause();
}
//抛出事件
}, {
key: "emit",
value: function emit(name) {
var _this$_event;
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
this._event['on' + name] && (_this$_event = this._event)['on' + name].apply(_this$_event, args);
}
//移除视频
}, {
key: "remove",
value: function remove() {
var _this$canvas2;
(_this$canvas2 = this.canvas) === null || _this$canvas2 === void 0 || _this$canvas2.remove();
this.canvas = null;
}
}]);
}(); //兼容new Function为了挂载到window对象上
if (typeof window != 'undefined') {
window.YbMpeg = YbMpeg;
}

View File

@@ -0,0 +1,225 @@
"use strict";
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
var YbPano = /*#__PURE__*/function () {
function YbPano(player) {
var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
_classCallCheck(this, YbPano);
this.player = player;
this.config = _objectSpread(_objectSpread({}, YbPano.DEFAULT_CONFIG), config);
this.scene = null;
this.camera = null;
this.renderer = null;
this.geometry = null; //几何体
this.material = null; //材质
this.sphere = null;
this.videoTexture = null; //视频纹理
this.controls = null; //摄像机控制器
this._animated = null; //动画实例
}
return _createClass(YbPano, [{
key: "init",
value: function init() {
var container = this.player.container;
var boxWidth = container.offsetWidth;
var boxHeight = container.offsetHeight;
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(75, boxWidth / boxHeight, 0.1, 1000);
this.renderer = new THREE.WebGLRenderer({
preserveDrawingBuffer: true //不设置该属性,则不能将渲染结果保存为图片
});
this.renderer.setSize(boxWidth, boxHeight);
var wrapperEl = container.getElementsByClassName('yb-player-wrapper')[0];
var div = document.createElement('DIV');
div.setAttribute('class', 'yb-player-pano');
div.setAttribute('style', 'position:absolute;inset: 0;visibility: hidden;'); //第一次加载不显示场景,因为考虑到视频需要显示封面,等待播放之后再显示
div.appendChild(this.renderer.domElement);
wrapperEl.insertBefore(div, wrapperEl.getElementsByClassName('yb-player-danmu')[0]);
this.camera.position.z = 5; //相机位置
this.loadControls();
}
//加载控制器
}, {
key: "loadControls",
value: function loadControls() {
//如果设备支持陀螺仪
if (this.config.controlsType == 'orientation' && typeof DeviceOrientationEvent !== 'undefined') {
this.controls = new THREE.DeviceOrientationControls(this.camera);
this.requestOrientationPermission();
//陀螺仪控制可以开启手势事件
this.player.enableGesture();
} else {
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
this.controls.enableZoom = false;
this.controls.enablePan = false;
this.controls.enableDamping = this.config.enableDamping;
if (!this.config.disableDragReverse) this.controls.rotateSpeed = -0.25;
if (this.player.three == '180') {
this.controls.minAzimuthAngle = 0;
this.controls.maxAzimuthAngle = 180;
this.controls.minPolarAngle = 0;
this.controls.maxPolarAngle = 180;
}
//禁用手势事件避免冲突
this.player.disableGesture();
//如果尝试开启陀螺仪控制,但失败了,则弹出提示
if (this.config.controlsType == 'orientation') {
this.player.showToast('该设备或当前环境可能不支持陀螺仪,已自动切换为滑动');
this.config.controlsType = 'orbit';
}
}
}
//卸载控制器
}, {
key: "unloadControls",
value: function unloadControls() {
if (this.controls) {
this.controls.disconnect && this.controls.disconnect();
this.controls.dispose();
this.controls = null;
}
}
//获取控制器类型
}, {
key: "getControlsType",
value: function getControlsType() {
return this.controls ? this.controls.deviceOrientation ? 'orientation' : 'orbit' : 'close';
}
}, {
key: "setControlsType",
value: function setControlsType(type) {
this.config.controlsType = type;
this.unloadControls();
this.loadControls();
}
//请求陀螺仪权限
}, {
key: "requestOrientationPermission",
value: function requestOrientationPermission() {
var _this = this;
if (typeof DeviceOrientationEvent !== 'undefined' && typeof DeviceOrientationEvent.requestPermission === 'function') {
DeviceOrientationEvent.requestPermission().then(function (permissionState) {
if (permissionState === 'granted') {
// 用户授予权限
_this.controls.connect(); // 现在才连接和启用控件
} else {
_this.player.showToast('必须给予陀螺仪权限才能使用陀螺仪');
}
})["catch"](function (err) {
_this.player.showToast('请求陀螺仪权限失败');
});
} else {
// 理论上这个分支应该已经被 init 函数处理了,但作为备用
this.controls.connect();
}
}
//开始渲染
}, {
key: "animate",
value: function animate() {
var _this2 = this;
this._cancelAnimationFrame();
var video = this.player.video;
if (video.readyState === video.HAVE_ENOUGH_DATA) this.videoTexture.needsUpdate = true;
if (this.controls && this.controls.enabled) this.controls.update();
//判断是否有["__v_raw"]属性如果有传入该属性值否则直接传入this.scene这里是为了兼容vue3因为vue3会将这些变量变为响应式数据导致内部一些只读变量报错
//该方法来自于vue3的toRaw方法
var scene = this.scene["__v_raw"] ? this.scene["__v_raw"] : this.scene;
this.renderer.render(scene, this.camera);
this._animated = window.requestAnimationFrame(function () {
return _this2.animate();
});
}
//更新尺寸
}, {
key: "updateSize",
value: function updateSize() {
var container = this.player.container;
var boxWidth = container.offsetWidth;
var boxHeight = container.offsetHeight;
this.camera.aspect = boxWidth / boxHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(boxWidth, boxHeight);
}
//创建视频纹理
}, {
key: "createVideoSphere",
value: function createVideoSphere() {
var video = this.player.video;
this.videoTexture = new THREE.VideoTexture(video);
this.geometry = this.player.three == '360' ? new THREE.SphereGeometry(500, 60, 40) : new THREE.SphereGeometry(500, 60, 40, 0, Math.PI, 0, Math.PI);
this.geometry.scale(-1, 1, 1);
this.material = new THREE.MeshBasicMaterial({
map: this.videoTexture
});
this.sphere = new THREE.Mesh(this.geometry, this.material);
this.scene.add(this.sphere);
}
//回收内存
}, {
key: "dispose",
value: function dispose() {
this._cancelAnimationFrame();
this.unloadControls();
if (this.videoTexture) {
this.videoTexture.dispose();
this.videoTexture = null;
}
if (this.geometry) {
this.geometry.dispose();
this.geometry = null;
}
if (this.material) {
this.material.dispose();
this.material = null;
}
if (this.sphere) {
this.sphere.remove();
this.sphere = null;
}
if (this.renderer) {
this.renderer.dispose();
this.renderer = null;
}
if (this.camera) {
this.camera.remove();
this.camera = null;
}
if (this.scene) {
this.scene.remove();
this.scene = null;
}
var container = this.player.container;
var threeEl = container.getElementsByClassName('yb-player-pano')[0];
if (threeEl) threeEl.remove();
threeEl = null;
}
//取消渲染动画
}, {
key: "_cancelAnimationFrame",
value: function _cancelAnimationFrame() {
if (this._animated) {
window.cancelAnimationFrame(this._animated);
this._animated = null;
}
}
}]);
}(); //兼容new Function为了挂载到window对象上
_defineProperty(YbPano, "DEFAULT_CONFIG", {
controlsType: 'orbit',
//控制器类型 orbit-手指或鼠标拖动 orientation-陀螺仪控制
disableDragReverse: false,
//关闭反方向拖拽旋转(反向拖拽更符合人的直觉)
enableDamping: true //添加阻尼效果(滑动更顺畅)
});
if (typeof window != 'undefined') {
window.YbPano = YbPano;
}

View File

@@ -0,0 +1,440 @@
"use strict";
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
/*字幕处理*/
var YbSubtitle = /*#__PURE__*/function () {
function YbSubtitle(player, list, config) {
_classCallCheck(this, YbSubtitle);
this.player = player;
this.list = list || [];
this.config = config || {};
this.activeIndex = -1; //渲染字幕位置索引
this.paused = true; //停止渲染
this._animation = null; //渲染动画
}
//默认配置
return _createClass(YbSubtitle, [{
key: "unload",
value: function unload() {
this.pause();
var container = this.player.container;
var div = container.getElementsByClassName('yb-player-subtitle-text')[0];
if (div) div.remove();
}
}, {
key: "setConfig",
value: function setConfig(key, value) {
this.config[key] = value;
}
}, {
key: "play",
value: function play() {
this.paused = false;
this._render();
}
}, {
key: "pause",
value: function pause() {
this.paused = true;
if (this._animation) {
window.cancelAnimationFrame(this._animation);
this._animation = null;
}
}
//渲染字幕
}, {
key: "_render",
value: function _render() {
var _this = this;
var container = this.player.container;
var video = this.player.video;
if (!video || !container) return;
var div = container.getElementsByClassName('yb-player-subtitle-text')[0];
var wrapperEl = container.getElementsByClassName('yb-player-wrapper')[0];
var nowIndex = this.list.findIndex(function (item) {
return YbPlayer.timeToSeconds(item.startTime) <= video.currentTime && YbPlayer.timeToSeconds(item.endTime) >= video.currentTime;
});
var config = _objectSpread(_objectSpread({}, YbSubtitle.DEFAULT_CONFIG), this.config);
if (nowIndex > -1 && this.activeIndex != nowIndex) {
this.activeIndex = nowIndex;
var nowTitle = this.list[nowIndex];
if (!div) {
div = document.createElement('DIV');
div.setAttribute('class', 'yb-player-subtitle-text');
wrapperEl.appendChild(div);
}
div.style.color = config.color;
div.style.fontSize = config.fontSize + 'px';
div.style.bottom = config.bottom;
div.style.textShadow = "0 0 5px " + config.shadowColor;
div.innerHTML = "<span>".concat(nowTitle.text, "</span>");
} else {
if (div && nowIndex == -1) div.remove();
}
if (!this.paused) this._animation = window.requestAnimationFrame(function () {
return _this._render();
});
}
}], [{
key: "init",
value: function init(player, src, config) {
return new Promise(function (resolve) {
YbSubtitle.showLoading(player);
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
YbSubtitle.showResult(player, '加载字幕文件成功');
var list = src.includes('.srt') ? YbSubtitle.parseSrt(xhr.responseText) : src.includes('.ass') ? YbSubtitle.parseAss(xhr.responseText).events.data.map(function (item) {
var text = item.Text.replace(/\{.*?\}/g, ''); // 移除{}标签
text = text.replace(/\\N/g, '<br>'); // 处理换行
return _objectSpread(_objectSpread({}, item), {}, {
text: text,
startTime: item.Start,
endTime: item.End
});
}) : YbSubtitle.parseVtt(xhr.responseText).cues;
resolve(new YbSubtitle(player, list, config));
} else {
YbSubtitle.showResult(player, '加载字幕文件失败');
resolve(null);
}
xhr.abort();
}
};
xhr.onabort = function () {
xhr = null;
};
xhr.open('GET', src);
xhr.responseType = 'text';
xhr.send();
});
}
}, {
key: "showLoading",
value: function showLoading(player) {
var container = player.container;
var wrapperEl = container ? container.getElementsByClassName('yb-player-wrapper')[0] : null;
if (wrapperEl) {
var div = document.createElement('DIV');
div.setAttribute('class', 'yb-player-subtitle-loading');
div.innerHTML = '正在加载字幕文件';
wrapperEl.appendChild(div);
}
}
}, {
key: "hideLoading",
value: function hideLoading(player) {
var container = player === null || player === void 0 ? void 0 : player.container;
var loadingEl = container ? container.getElementsByClassName('yb-player-subtitle-loading')[0] : null;
if (loadingEl) loadingEl.remove();
}
}, {
key: "showResult",
value: function showResult(player, message) {
var container = player.container;
var loadingEl = container ? container.getElementsByClassName('yb-player-subtitle-loading')[0] : null;
if (loadingEl) loadingEl.innerHTML = message;
window.setTimeout(function () {
YbSubtitle.hideLoading(player);
}, 1000);
}
//格式化SRT字幕
}, {
key: "parseSrt",
value: function parseSrt(content) {
// 按空行分割字幕块
var blocks = content.trim().split(/\n\s*\n/);
var result = [];
for (var i = 0; i < blocks.length; i++) {
var lines = blocks[i].split('\n').filter(function (line) {
return line.trim() !== '';
});
if (lines.length < 3) {
continue; // 跳过无效块
}
// 解析序号
var index = parseInt(lines[0]);
if (isNaN(index)) {
throw new Error("\u65E0\u6548\u7684\u5E8F\u53F7: ".concat(lines[0]));
}
// 解析时间码
var timecodeMatch = lines[1].match(/(\d{2}):(\d{2}):(\d{2}),(\d{3})\s*-->\s*(\d{2}):(\d{2}):(\d{2}),(\d{3})/);
if (!timecodeMatch) {
throw new Error("\u65E0\u6548\u7684\u65F6\u95F4\u7801\u683C\u5F0F: ".concat(lines[1]));
}
var startTime = "".concat(parseInt(timecodeMatch[1]), ":").concat(parseInt(timecodeMatch[2]), ":").concat(parseInt(timecodeMatch[3]), ".").concat(parseInt(timecodeMatch[4]));
var endTime = "".concat(parseInt(timecodeMatch[5]), ":").concat(parseInt(timecodeMatch[6]), ":").concat(parseInt(timecodeMatch[7]), ".").concat(parseInt(timecodeMatch[8]));
// 合并文本行
var text = lines.slice(2).join('\n');
result.push({
index: index,
startTime: startTime,
endTime: endTime,
text: text
});
}
return result;
}
}, {
key: "parseAss",
value: function parseAss(content) {
var lines = content.split('\n');
var currentSection = '';
var result = {
info: {},
styles: {
format: [],
data: []
},
events: {
format: [],
data: []
}
};
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim();
// 跳过空行和注释
if (!line || line.startsWith(';')) continue;
// 检测段落开始
if (line.startsWith('[') && line.endsWith(']')) {
currentSection = line.slice(1, -1).toLowerCase();
continue;
}
// 根据当前段落处理内容
switch (currentSection) {
case 'script info':
parseScriptInfo(line, result.info);
break;
case 'v4+ styles':
parseStyles(line, result.styles);
break;
case 'events':
parseEvents(line, result.events);
break;
}
}
return result;
}
}, {
key: "parseVtt",
value: function parseVtt(content) {
var lines = content.split('\n');
var cues = [];
var currentCue = null;
var note = null;
var style = null;
var region = null;
// 检查文件头
if (lines.length === 0 || !lines[0].includes('WEBVTT')) {
throw new Error('无效的VTT文件: 缺少WEBVTT文件头');
}
// 解析VTT内容
for (var i = 1; i < lines.length; i++) {
var line = lines[i].trim();
// 跳过空行
if (line === '') continue;
// 检查是否是时间轴
if (line.includes('-->')) {
// 如果已有当前cue先保存它
if (currentCue) {
cues.push(currentCue);
}
// 创建新的cue
currentCue = {
identifier: null,
start: null,
end: null,
text: '',
styles: null
};
// 解析时间轴
var arrowIndex = line.indexOf('-->');
currentCue.startTime = line.substring(0, arrowIndex).trim();
currentCue.endTime = line.substring(arrowIndex + 3, arrowIndex + 15).trim();
var settingsLine = line.substring(arrowIndex + 15).trim();
// 解析设置(如果有)
if (settingsLine) currentCue.styles = parseCueSettings(settingsLine);
}
// 检查是否是标识符(数字或文本)
else if (!currentCue && /^\d+$/.test(line)) {
// 这是标识符行
if (currentCue) {
currentCue.identifier = line;
}
}
// 检查是否是注释
else if (line.startsWith('NOTE')) {
note = line.substring(4).trim();
}
// 检查是否是样式块
else if (line.startsWith('STYLE')) {
style = line.substring(5).trim();
}
// 检查是否是区域块
else if (line.startsWith('REGION')) {
region = line.substring(6).trim();
}
// 否则是文本行
else if (currentCue) {
if (currentCue.text) {
currentCue.text += '\n' + line;
} else {
currentCue.text = line;
}
}
}
// 添加最后一个cue
if (currentCue) {
cues.push(currentCue);
}
return {
metadata: {
note: note,
style: style,
region: region
},
cues: cues
};
}
}]);
}(); // 解析脚本信息
_defineProperty(YbSubtitle, "DEFAULT_CONFIG", {
color: '#ffffff',
//文字颜色
fontSize: 18,
//字体大小
bottom: '10%',
//底部边距
shadowColor: 'rgba(0,0,0,.5)' //阴影颜色
});
function parseScriptInfo(line, infoObj) {
var colonIndex = line.indexOf(':');
if (colonIndex === -1) return;
var key = line.substring(0, colonIndex).trim();
var value = line.substring(colonIndex + 1).trim();
infoObj[key] = value;
}
// 解析样式
function parseStyles(line, stylesObj) {
if (line.toLowerCase().startsWith('format:')) {
// 解析格式行
var formatLine = line.substring(7).trim();
stylesObj.format = formatLine.split(',').map(function (item) {
return item.trim();
});
} else if (line.toLowerCase().startsWith('style:')) {
// 解析样式数据
var styleLine = line.substring(6).trim();
var values = parseCsvLine(styleLine);
if (values.length === stylesObj.format.length) {
var style = {};
stylesObj.format.forEach(function (key, index) {
style[key] = values[index];
});
stylesObj.data.push(style);
}
}
}
// 解析事件
function parseEvents(line, eventsObj) {
if (line.toLowerCase().startsWith('format:')) {
// 解析格式行
var formatLine = line.substring(7).trim();
eventsObj.format = formatLine.split(',').map(function (item) {
return item.trim();
});
} else if (line.toLowerCase().startsWith('dialogue:') || line.toLowerCase().startsWith('comment:')) {
// 解析对话或注释数据
var typeEnd = line.indexOf(':');
var type = line.substring(0, typeEnd).trim();
var dataLine = line.substring(typeEnd + 1).trim();
var values = parseCsvLine(dataLine);
if (values.length === eventsObj.format.length) {
var event = {
Type: type
};
eventsObj.format.forEach(function (key, index) {
event[key] = values[index];
});
eventsObj.data.push(event);
}
}
}
// 解析CSV格式的行处理逗号在引号内的情况
function parseCsvLine(line) {
var result = [];
var current = '';
var inQuotes = false;
for (var i = 0; i < line.length; i++) {
var _char = line[i];
if (_char === '"') {
inQuotes = !inQuotes;
} else if (_char === ',' && !inQuotes) {
result.push(current.trim());
current = '';
} else {
current += _char;
}
}
result.push(current.trim());
return result;
}
// 解析Cue设置
function parseCueSettings(settingsLine) {
var settings = {};
var parts = settingsLine.split(' ');
var _iterator = _createForOfIteratorHelper(parts),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var part = _step.value;
var _part$split = part.split(':'),
_part$split2 = _slicedToArray(_part$split, 2),
key = _part$split2[0],
value = _part$split2[1];
if (key && value) {
settings[key] = value;
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
return settings;
}
//兼容new Function为了挂载到window对象上
if (typeof window != 'undefined') {
window.YbSubtitle = YbSubtitle;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,134 @@
/**
* @author richt / http://richt.me
* @author WestLangley / http://github.com/WestLangley
*
* W3C Device Orientation control (http://w3c.github.io/deviceorientation/spec-source-orientation.html)
*/
THREE.DeviceOrientationControls = function ( object ) {
var scope = this;
this.object = object;
this.object.rotation.reorder( 'YXZ' );
this.enabled = true;
this.deviceOrientation = {};
this.screenOrientation = 0;
this.alphaOffset = 0; // radians
var onDeviceOrientationChangeEvent = function ( event ) {
scope.deviceOrientation = event;
};
var onScreenOrientationChangeEvent = function () {
scope.screenOrientation = window.orientation || 0;
};
// The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y''
var setObjectQuaternion = function () {
var zee = new THREE.Vector3( 0, 0, 1 );
var euler = new THREE.Euler();
var q0 = new THREE.Quaternion();
var q1 = new THREE.Quaternion( - Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) ); // - PI/2 around the x-axis
return function ( quaternion, alpha, beta, gamma, orient ) {
euler.set( beta, alpha, - gamma, 'YXZ' ); // 'ZXY' for the device, but 'YXZ' for us
quaternion.setFromEuler( euler ); // orient the device
quaternion.multiply( q1 ); // camera looks out the back of the device, not the top
quaternion.multiply( q0.setFromAxisAngle( zee, - orient ) ); // adjust for screen orientation
};
}();
this.connect = function () {
onScreenOrientationChangeEvent(); // run once on load
// iOS 13+
if ( window.DeviceOrientationEvent !== undefined && typeof window.DeviceOrientationEvent.requestPermission === 'function' ) {
window.DeviceOrientationEvent.requestPermission().then( function ( response ) {
if ( response == 'granted' ) {
window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent, false );
window.addEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false );
}
} ).catch( function ( error ) {
console.error( 'THREE.DeviceOrientationControls: Unable to use DeviceOrientation API:', error );
} );
} else {
window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent, false );
window.addEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false );
}
scope.enabled = true;
};
this.disconnect = function () {
window.removeEventListener( 'orientationchange', onScreenOrientationChangeEvent, false );
window.removeEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false );
scope.enabled = false;
};
this.update = function () {
if ( scope.enabled === false ) return;
var device = scope.deviceOrientation;
if ( device ) {
var alpha = device.alpha ? THREE.MathUtils.degToRad( device.alpha ) + scope.alphaOffset : 0; // Z
var beta = device.beta ? THREE.MathUtils.degToRad( device.beta ) : 0; // X'
var gamma = device.gamma ? THREE.MathUtils.degToRad( device.gamma ) : 0; // Y''
var orient = scope.screenOrientation ? THREE.MathUtils.degToRad( scope.screenOrientation ) : 0; // O
setObjectQuaternion( scope.object.quaternion, alpha, beta, gamma, orient );
}
};
this.dispose = function () {
scope.disconnect();
};
this.connect();
};

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,285 @@
<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover">
<script type="text/javascript" src="js/hls.min.js"></script>
<script type="text/javascript" src="js/flv.min.js"></script>
<script type="text/javascript" src="js/jsmpeg.min.js"></script>
<script type="text/javascript" src="js/three.min.js"></script>
<script type="text/javascript" src="js/OrbitControls.js"></script>
<script type="text/javascript" src="js/DeviceOrientationControls.js"></script>
<script type="text/javascript" src="js/uni-webview-js@1.5.4.js"></script>
<script type="text/javascript" src="dist/yb-player-gesture.js"></script>
<script type="text/javascript" src="dist/yb-player-subtitle.js"></script>
<script type="text/javascript" src="dist/yb-player-danmu.js"></script>
<script type="text/javascript" src="dist/yb-player-pano.js"></script>
<script type="text/javascript" src="dist/yb-player-mpeg.js"></script>
<script type="text/javascript" src="dist/yb-player.js"></script>
<link rel="stylesheet" type="text/css" href="css/yb-player.css"/>
<link rel="stylesheet" type="text/css" href="css/yb-player-plugin.css"/>
<title>全局播放器</title>
<style>
html, body {
padding: 0;
margin: 0;
background-color: #000;
width: 100vw;
height: 100vh;
}
.btns {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: none;
flex-direction: row;
}
.yb-player {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<!-- Specify the player container -->
<div id="yb-player" class="yb-player">
</div>
<script>
var mp
//加载视频
function reloadVideo (arg) {
var params = parseArg(arg)
unload()
_traverseObject(params.custom, 'slotclick')
mp = new YbPlayer({
container:'#yb-player',
src: params.src,
segments: params.segments,
title: params.title,
poster: params.poster || undefined,
type: params.type,
three: params.three,
initialTime: params.initialTime,
duration: params.duration,
autoplay: params.autoplay,
preload: params.preload,
muted: params.muted,
playbackRate: params.playbackRate,
loop: params.loop,
isLive: params.isLive,
header: params.header,
controls: params.controls,
alwaysShowControls: params.alwaysShowControls,
height: '100%',
objectFit: params.objectFit,
crossOrigin: params.crossOrigin,
openDirection: params.openDirection,
exitDirection: params.exitDirection,
quality: params.quality,
works: params.works,
workIndex: params.workIndex,
subtitles: params.subtitles,
subtitleIndex: params.subtitleIndex,
custom: params.custom,
decoder: {
hls: {
loader: Hls,
config: parseHlsConfig(params.hlsConfig)
},
flv: {
loader: flvjs,
config: params.flvConfig
},
jsmpeg: {
loader: JSMpeg,
config: params.jsmpegConfig
}
}
})
mp.load()
mp.loadVideo()
mp.loadGestureEvent()
mp.onmessage = function (data) {
uni.postMessage({data})
}
window.addEventListener('resize', updateSize)
}
function updateSize () {
if( mp ) {
mp.refreshDanmu()
mp.refreshPano()
}
}
//动态修改video属性
function setVideo (key, value) {
if( mp ) mp.setVideo(key, value)
}
//加载弹幕
function loadDanmu () {
if ( mp ) {
if ( window.danmu && window.danmu.length ) mp.setConfig('danmu', window.danmu)
mp.unloadDanmu()
mp.loadDanmu()
window.dammu = null
}
}
//卸载弹幕
function unloadDanmu () {
if ( mp ) mp.unloadDanmu()
}
//发送弹幕
function sendDanmu (arg, border) {
var danmu = parseArg(arg)
if ( mp ) mp.sendDanmu(danmu, border)
}
//插入弹幕
function insertDanmu (arg) {
var danmu = parseArg(arg)
if ( mp ) mp.insertDanmu(danmu)
}
//更新配置
function updateConfig (arg) {
var config = parseArg(arg)
if ( mp ) {
Object.keys(config).forEach(key => {
mp.setConfig(key, config[key])
})
mp.hideControls()
}
}
//重加载自定义配置
function reloadCustom (arg) {
var config = parseArg(arg)
_traverseObject(config, 'slotclick')
if ( mp ) {
Object.keys(config).forEach(key => {
mp.setCustom(key, config[key])
})
mp.unloadCustom()
mp.loadCustom()
}
}
//播放/暂停
function toggle () {
if ( mp ) mp.toggle()
}
//播放
function play () {
if ( mp ) mp.video.play()
}
//暂停
function pause () {
if ( mp ) mp.video.pause()
}
//跳转
function seek (time) {
if ( mp ) mp.seek(time)
}
//开启全屏
function openFullscreen (direction) {
if ( mp ) mp.openFullscreen(direction)
}
//退出全屏
function exitFullscreen () {
if ( mp ) mp.exitFullscreen()
}
//消息提示
function showToast (arg) {
var data = parseArg(arg)
if ( mp ) mp.showToast(data)
}
//展示工具栏
function showToolbar (arg) {
var data = parseArg(arg)
_traverseObject(data, 'toolclick')
if ( mp ) mp.showToolbar(data.selector, data.list, data.checkShow, data.checkIndex)
}
//截图
function capture (arg) {
var data = parseArg(arg)
if ( mp ) mp.capture(data.type, data.show)
}
//禁用手势事件
function disableGesture () {
if ( mp ) mp.disableGesture()
}
//启用手势事件
function enableGesture () {
if ( mp ) mp.enableGesture()
}
//卸载视频
function unload () {
if ( mp ) {
mp.unloadDanmu()
mp.unloadGestureEvent()
mp.unloadVideo()
mp.unload()
window.removeEventListener('resize', updateSize)
}
}
//卸载视频
function destroy () {
unload()
uni.postMessage({data: {destroyed: true}})
}
//处理custom
function _traverseObject(obj, emitname) {
if (typeof obj !== 'object' || obj === null) {
return;
}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
if (key == 'click') {
var emit = {}
emit[emitname] = value
obj[key] = function () { uni.postMessage({data: emit}) }//点击事件通知
} else if (typeof value === 'object' && value !== null) {
_traverseObject(value, emitname);
}
}
}
}
//处理hlsConfig
function parseHlsConfig (config = {}) {
Object.keys(config).forEach(key => {
if ( ['xhrSetup'].includes(key) ) {
config[key] = eval(`(${config[key]})`);
}
if ( ['pLoader', 'fLoader'].includes(key) ) {
config[key] = (new Function(`return ${config[key]}`))();
}
})
return config
}
//设置弹幕数据webview专用
function setDanmuData (arg) {
var { code, data } = parseArg(arg)
if ( !window.danmu ) window.danmu = []
window.danmu = window.danmu.concat(data)
if ( code == 1 ) loadDanmu()
}
//转义参数
function parseArg (arg) {
try{
return JSON.parse(decodeURIComponent(decodeURIComponent(arg)))
}catch(e){
return arg
}
}
//通知app已经做好准备
function ready () {
uni.postMessage({data: {ready: true}})
}
if (document.readyState === 'complete') {
// 如果页面已经加载完成,直接执行函数
ready();
} else {
window.addEventListener('load', ready)
}
</script>
</body>
</html>