@jxstjh/jhvideo
Version:
HTML5 jhvideo base on MPEG2-TS Stream Player
496 lines (456 loc) • 17 kB
JavaScript
import Hammer from 'hammerjs'
import EventEmitter from 'events';
import { debounceTime, map, throttleTime } from '../../node_modules/rxjs/operators';
import { Observable, fromEvent } from '../../node_modules/rxjs';
function initZoom(begin, end) {
const abs = end - begin;
const h = Math.floor(abs / (1000 * 60 * 60));
if (h <= 2) {
return 2
} else if (h > 2 && h <= 6) {
return 6
} else if (h > 6 && h <= 12) {
return 12
} else if (h > 12 && h <= 24) {
return 24
} else {
return Math.floor(h / 24) * 24
}
}
var TimeSlider = function (canvas, options) {
this.emitter = new EventEmitter();
this.canvas = canvas;
this.ctx = this.canvas.getContext("2d");
this.canvansW = this.canvas.width;
this.canVansH = this.canvas.height;
// this.canvansW = clientWidth ;
// this.canVansH = clientHeight;
this._origin_start_timestamp = options.start_timestamp;
this.start_timestamp = options.start_timestamp;
this.end_timestamp = options.end_timestamp;
// this.timecell = options.init_cells;
this.timecell = [{
"beginTime": this._origin_start_timestamp,
"endTime": this.end_timestamp,
"style": {
"background": "rgba(74, 141, 240, .8)"
}
}]
this.minutes_per_step = [1, 2, 5, 10, 15, 20, 30, 60, 120, 180, 240, 360, 720, 1440]; // min/格
this.graduation_step = 15;//刻度间最小宽度,单位px
// this.hours_per_ruler = 24;//时间轴显示24小时
this.hours_per_ruler = initZoom(this.start_timestamp, this.end_timestamp);
this.distance_between_gtitle = 40;
this.zoom = initZoom(this.start_timestamp, this.end_timestamp);
this.g_isMousedown = false;//拖动mousedown标记
this.g_isMousemove = false;//拖动mousemove标记
this.g_mousedownCursor = null;//拖动mousedown的位置
this.returnTime = null;//mouseup返回时间
this.on_before_click_ruler_callback = null;
this.initHammer()
this.init(this.start_timestamp, this.timecell, false);
return this;
};
TimeSlider.prototype.on = function (event, listener) {
this.emitter.addListener(event, listener);
}
TimeSlider.prototype.off = function (event, listener) {
this.emitter.removeListener(event, listener);
}
/**
* 初始化手势
*/
TimeSlider.prototype.initHammer = function () {
const { _rootHammertime } = this;
if (_rootHammertime) {
_rootHammertime.destroy()
}
const canvas = this.canvas;
const mc = new Hammer.Manager(canvas, {
});
const pan = new Hammer.Pan({ threshold: 16, direction: Hammer.DIRECTION_HORIZONTAL });
mc.add([pan]);
const pinch = new Hammer.Pinch();
mc.add(pinch);
this._rootHammertime = mc
}
/**
* 初始化
* @param {*} start_timestamp 最左侧时间
* @param {*} timecell 录像段数组
* @param {*} redrawFlag 是否重绘标记
*/
TimeSlider.prototype.init = function (start_timestamp, timecell, redrawFlag) {
this.timecell = timecell;
this.start_timestamp = start_timestamp;
// this.drawCellBg();
this.add_graduations(start_timestamp);
this.add_cells(timecell);
// this.drawLine(0,this.canVansH,this.canvansW,this.canVansH,"red",1); //底线
if (!redrawFlag) {//只有第一次进入才需要添加事件
this.add_events();
}
this.drawLine(this.canvansW / 2, 0, this.canvansW / 2, 33, "rgb(64, 196, 255", 2); //中间播放点时间线
var time = start_timestamp + (this.hours_per_ruler * 3600 * 1000) / 2;
this.ctx.fillStyle = "rgba(255,255,255,1)";
// this.ctx.fillText(this.changeTime(time),this.canvansW/2-60,50);
}
/**
* 绘制添加刻度
* @param {*} start_timestamp 最左侧时间
*/
TimeSlider.prototype.add_graduations = function (start_timestamp) {
var _this = this;
var px_per_min = _this.canvansW / (_this.hours_per_ruler * 60); // px/min
var px_per_ms = _this.canvansW / (_this.hours_per_ruler * 60 * 60 * 1000); // px/ms
var px_per_step = _this.graduation_step; // px/格 默认最小值20px
var min_per_step = px_per_step / px_per_min; // min/格
for (var i = 0; i < _this.minutes_per_step.length; i++) {
if (min_per_step <= _this.minutes_per_step[i]) { //让每格时间在minutes_per_step规定的范围内
min_per_step = _this.minutes_per_step[i];
px_per_step = px_per_min * min_per_step;
break
}
}
var medium_step = 30;
for (var i = 0; i < _this.minutes_per_step.length; i++) {
if (_this.distance_between_gtitle / px_per_min <= _this.minutes_per_step[i]) {
medium_step = _this.minutes_per_step[i];
break;
}
}
var num_steps = _this.canvansW / px_per_step; //总格数
var graduation_left;
var graduation_time;
var caret_class;
var lineH;
var ms_offset = _this.ms_to_next_step(start_timestamp, min_per_step * 60 * 1000);//开始的偏移时间 ms
var px_offset = ms_offset * px_per_ms; //开始的偏移距离 px
var ms_per_step = px_per_step / px_per_ms; // ms/step
for (var i = 0; i < num_steps; i++) {
graduation_left = px_offset + i * px_per_step; // 距离=开始的偏移距离+格数*px/格
graduation_time = start_timestamp + ms_offset + i * ms_per_step; //时间=左侧开始时间+偏移时间+格数*ms/格
var date = new Date(graduation_time);
const boundary = (graduation_time >= _this._origin_start_timestamp) && (graduation_time <= _this.end_timestamp)
if (date.getUTCHours() == 0 && date.getUTCMinutes() == 0) {
caret_class = 'big';
lineH = 25;
var big_date = _this.graduation_title(date);
_this.ctx.fillStyle = boundary ? "rgba(255,255,255,1)" : "rgba(255,255,255,.2)";
_this.ctx.fillText(big_date, graduation_left - 20, 30);
} else if (graduation_time / (60 * 1000) % medium_step == 0) {
caret_class = 'middle';
lineH = 15;
var middle_date = _this.graduation_title(date);
_this.ctx.fillStyle = boundary ? "rgba(255,255,255,1)" : "rgba(255,255,255,.2)";
_this.ctx.fillText(middle_date, graduation_left - 15, 30);
} else {
lineH = 10;
}
// drawLine(graduation_left,0,graduation_left,lineH,"rgba(151,158,167,0.4)",1);
// _this.drawLine(graduation_left, 0, graduation_left, lineH, boundary ? "rgba(151,158,167,1)" : "rgba(151,158,167,.2)", 1);
}
}
/**
* 绘制线
* @param {*} beginX
* @param {*} beginY
* @param {*} endX
* @param {*} endY
* @param {*} color
* @param {*} width
*/
TimeSlider.prototype.drawLine = function (beginX, beginY, endX, endY, color, width) {
this.ctx.beginPath();
this.ctx.moveTo(beginX, beginY);
this.ctx.lineTo(endX, endY);
this.ctx.strokeStyle = color;
this.ctx.lineWidth = width;
this.ctx.stroke();
}
/**
* 添加录像段
* @param {*} cells 录像数组
*/
TimeSlider.prototype.add_cells = function (cells) {
var _this = this;
cells.forEach(cell => {
_this.draw_cell(cell)
});
}
/**
* 绘制录像块
* @param {*} cell cell包括beginTime ms;endTime ms;style
*/
TimeSlider.prototype.draw_cell = function (cell) {
var _this = this;
var px_per_ms = _this.canvansW / (_this.hours_per_ruler * 60 * 60 * 1000); // px/ms
var beginX = (cell.beginTime - _this.start_timestamp) * px_per_ms;
var cell_width = (cell.endTime - cell.beginTime) * px_per_ms;
_this.ctx.fillStyle = cell.style.background;
_this.ctx.fillRect(beginX, 0, cell_width, 15);
}
/**
* 绘制录像块背景
*/
TimeSlider.prototype.drawCellBg = function () {
this.ctx.fillStyle = "rgba(69, 72, 76, 0.5)";
this.ctx.fillRect(0, 0, this.canvansW, 15);
}
/**
* 时间轴事件
*/
TimeSlider.prototype.add_events = function () {
var _this = this;
const { _rootHammertime } = this;
_rootHammertime.on('panstart', _this.mousedownFunc.bind(_this))
_rootHammertime.on('panmove', _this.mousemoveFunc.bind(_this))
_rootHammertime.on('panend', _this.mouseupFunc.bind(_this))
this._resizeEventFn = _this.resizeCanvas.bind(this)
const $obResize = fromEvent(window, 'resize').pipe(
debounceTime(100)
).subscribe(this._resizeEventFn)
// window.addEventListener('resize', this._resizeEventFn)
const $obPinch = new Observable(subscriber => {
_rootHammertime.on('pinchin pinchout', e => {
subscriber.next(e);
})
}).pipe(
throttleTime(100),
map(e => {
_this.mousewheelFunc(e)
}),
debounceTime(52)
).subscribe(() => {
this._pinching = false
}
)
if (_this.canvas.addEventListener) {
// _this.canvas.addEventListener('mousewheel', _this.mousewheelFunc.bind(_this));
// _this.canvas.addEventListener('mousedown', _this.mousedownFunc.bind(_this));
// _this.canvas.addEventListener('mousemove', _this.mousemoveFunc.bind(_this));
// _this.canvas.addEventListener('mouseup', _this.mouseupFunc.bind(_this));
// _this.canvas.addEventListener('mouseout', _this.mouseoutFunc.bind(_this));
}
}
/**
* 拖动/点击 mousedown事件
*/
TimeSlider.prototype.mousedownFunc = function (e) {
console.log('mousedownFunc', e)
this.g_isMousedown = true;
this.g_mousedownCursor = this.get_cursor_x_position(e.srcEvent);//记住mousedown的位置
}
/**
* 拖动/鼠标hover显示 mousemove事件
*/
TimeSlider.prototype.mousemoveFunc = function (e) {
const srcE = e.srcEvent || e
var _this = this;
var pos_x = _this.get_cursor_x_position(srcE);
var px_per_ms = _this.canvansW / (_this.hours_per_ruler * 60 * 60 * 1000); // px/ms
if (_this.g_isMousedown) {
var diff_x = pos_x - _this.g_mousedownCursor;
let _start_timestamp = _this.start_timestamp - Math.round(diff_x / px_per_ms);
// 判断边界
const _returnTime = _start_timestamp + (_this.hours_per_ruler * 3600 * 1000) / 2;
if (_returnTime < this._origin_start_timestamp || _returnTime > this.end_timestamp) {
return;
}
_this.clearCanvas();
_this.start_timestamp = _start_timestamp;
_this.init(_this.start_timestamp, _this.timecell, true);
_this.g_isMousemove = true;
_this.g_mousedownCursor = pos_x;
} else {
// var time = _this.start_timestamp + pos_x/px_per_ms;
_this.clearCanvas();
_this.init(_this.start_timestamp, _this.timecell, true);
// _this.drawLine(pos_x,0,pos_x,50,"rgb(194, 202, 215)",1);
// _this.ctx.fillStyle = "rgb(194, 202, 215)";
// _this.ctx.fillText(_this.changeTime(time),pos_x-50,60);
}
}
/**
* 拖动/点击 mouseup事件
*/
TimeSlider.prototype.mouseupFunc = function (e) {
var _this = this;
if (_this.g_isMousemove) { //拖动 事件
_this.g_isMousemove = false;
_this.g_isMousedown = false;
_this.returnTime = _this.start_timestamp + (_this.hours_per_ruler * 3600 * 1000) / 2;
const offsetT = _this.returnTime - this._origin_start_timestamp
this.emitter.emit('seekEnd', offsetT)
} else { // click 事件
_this.g_isMousedown = false;
var posx = _this.get_cursor_x_position(e.srcEvent); //鼠标距离 px
var ms_per_px = (_this.zoom * 3600 * 1000) / _this.canvansW; // ms/px
_this.returnTime = _this.start_timestamp + posx * ms_per_px;
_this.set_time_to_middle(_this.returnTime);
}
}
/**
* 鼠标移出隐藏时间 mouseout事件
* @param {*} e
*/
TimeSlider.prototype.mouseoutFunc = function () {
var _this = this;
_this.clearCanvas();
_this.init(_this.start_timestamp, _this.timecell, true);
}
/**
* 滚轮放大缩小,以时间轴中心为准 mousewheel事件
*/
TimeSlider.prototype.mousewheelFunc = function (event) {
var _this = this;
this._pinching = true;
console.log(event.type)
// if (event && event.preventDefault) {
// event.preventDefault()
// } else {
// window.event.returnValue = false;
// return false;
// }
var e = event;
// var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
var delta = e.type === 'pinchin' ? -1 : 1;
var middle_time = _this.start_timestamp + (_this.hours_per_ruler * 3600 * 1000) / 2; //ms 记住当前中间的时间
if (delta < 0) {
_this.zoom = _this.zoom + 4;
if (_this.zoom >= 24) {
_this.zoom = 24;//放大最大24小时
}
_this.hours_per_ruler = _this.zoom;
} else if (delta > 0) {// 放大
_this.zoom = _this.zoom - 4;
if (_this.zoom <= 1) {
_this.zoom = 1;//缩小最小1小时
}
_this.hours_per_ruler = _this.zoom;
}
_this.clearCanvas();
_this.start_timestamp = middle_time - (_this.hours_per_ruler * 3600 * 1000) / 2; //start_timestamp = 当前中间的时间 - zoom/2
_this.init(_this.start_timestamp, _this.timecell, true)
}
/**
* 获取鼠标posx
* @param {*} e
*/
TimeSlider.prototype.get_cursor_x_position = function (e) {
var posx = 0;
if (!e) {
e = window.event;
}
if (e.pageX || e.pageY) {
posx = e.pageX;
} else if (e.clientX || e.clientY) {
posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
}
return posx;
}
/**
* 返回时间轴上刻度的时间
* @param {*} datetime new Date 格式
*/
TimeSlider.prototype.graduation_title = function (datetime) {
if (datetime.getHours() == 0 && datetime.getMinutes() == 0 && datetime.getMilliseconds() == 0) {
return ('0' + datetime.getDate().toString()).substr(-2) + '.' +
('0' + (datetime.getMonth() + 1).toString()).substr(-2) + '.' +
datetime.getFullYear();
}
return datetime.getHours() + ':' + ('0' + datetime.getMinutes().toString()).substr(-2);
};
/**
* 返回 2018-01-01 10:00:00 格式时间
* @param {*} time
*/
TimeSlider.prototype.changeTime = function (time) {
var newTime = new Date(time);
var year = newTime.getFullYear();
var month = newTime.getMonth() + 1;
if (month < 10) {
var month = "0" + month;
}
var date = newTime.getDate();
if (date < 10) {
var date = "0" + date;
}
var hour = newTime.getHours();
if (hour < 10) {
var hour = "0" + hour;
}
var minute = newTime.getMinutes();
if (minute < 10) {
var minute = "0" + minute;
}
var second = newTime.getSeconds();
if (second < 10) {
var second = "0" + second;
}
return year + "-" + month + "-" + date + " " + hour + ":" + minute + ":" + second;
}
/**
* 左侧开始时间的偏移,返回单位ms
* @param {*} timestamp
* @param {*} step
*/
TimeSlider.prototype.ms_to_next_step = function (timestamp, step) {
var remainder = timestamp % step;
return remainder ? step - remainder : 0;
}
/**
* 设置时间,让这个时间点跳到中间红线处
* @param {*} time 单位ms
*/
TimeSlider.prototype.set_time_to_middle = function (time) {
if (this.g_isMousedown || this._pinching) {
return
}
this.clearCanvas();
this.start_timestamp = time - (this.hours_per_ruler * 60 * 60 * 1000) / 2;
this.init(this.start_timestamp, this.timecell, true)
}
/**
* 返回点击或者拖动的时间点
*/
TimeSlider.prototype.returnMouseupTime = function (callback) {
var _this = this;
if (_this.returnTime != null) {
if (callback) {
callback(_this.returnTime);
}
}
}
/**
* resize
*/
TimeSlider.prototype.resizeCanvas = function () {
this.canvas.width = 'auto'
this.canvas.height = 'auto'
setTimeout(() => {
this.clearCanvas();
const { clientWidth, clientHeight } = this.canvas
this.canvas.width = clientWidth
this.canvas.height = clientHeight
this.canvansW = this.canvas.width;
this.canVansH = this.canvas.height;
this.init(this.start_timestamp, this.timecell, true);
}, 30)
}
/**
* 清除canvas 每次重新绘制需要先清除
*/
TimeSlider.prototype.clearCanvas = function () {
this.ctx.clearRect(0, 0, 1500, 150);
}
/**
* 销毁
*/
TimeSlider.prototype.destroy = function () {
this.emitter && this.emitter.removeAllListeners();
if (this._rootHammertime) {
this._rootHammertime.destroy()
}
}
export default TimeSlider