diff --git a/index.html b/index.html
index 5e64d55..42b0f93 100644
--- a/index.html
+++ b/index.html
@@ -28,12 +28,17 @@
top: 0;
left: 0;
}
+ .vjs-marker{
+ white-space: normal;
+ width: 100px;
+ font-size: 2em;
+ }
-
@@ -50,11 +55,7 @@
return check;
}
(function(window, videojs) {
- var player = window.player = videojs('videojs-panorama-player', {
- poster: "assets/poster-360.jpg",
- plugins: {
- }
- });
+ var player = window.player = videojs('videojs-panorama-player', {});
var panorama = player.panorama({
PanoramaThumbnail: true,
@@ -72,9 +73,7 @@
lon: 180
},
radius: 500,
- element: "Marker 1",
- keyPoint: 5000,
- duration: 10000
+ element: "This is text 1 with long text"
},
{
location: {
@@ -82,9 +81,43 @@
lon: 160
},
radius: 500,
- element: "Marker 2",
+ element: "This is text 2 with long text",
+ onShow: function(){
+ console.log("text 2 is shown");
+ },
+ onHide: function(){
+ console.log("text 2 is hidden");
+ }
+ }
+ ],
+ Animation: [
+ {
+ keyPoint: 0,
+ from: {
+ lon: 180,
+ },
+ to:{
+ lon: 540,
+ },
+ duration: 8000,
+ ease: "linear",
+ onComplete: function () {
+ console.log("animation 1 is completed");
+ }
+ },
+ {
+ keyPoint: 0,
+ from: {
+ fov: 75,
+ },
+ to:{
+ fov: 90,
+ },
+ duration: 5000,
+ ease: "linear",
}
- ]
+ ],
+ VRGapDegree: 0
});
window.player = player;
diff --git a/package.json b/package.json
index 8856711..42df6e3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "videojs-panorama",
- "version": "0.1.6",
+ "version": "1.0.0",
"description": "a plugin for videojs run a full 360 degree panorama video. ",
"keywords": [
"videojs",
@@ -69,4 +69,4 @@
"video.js": "global:videojs",
"three": "global:THREE"
}
-}
+}
\ No newline at end of file
diff --git a/src/scripts/Components/Animation.js b/src/scripts/Components/Animation.js
new file mode 100644
index 0000000..6d3be7a
--- /dev/null
+++ b/src/scripts/Components/Animation.js
@@ -0,0 +1,169 @@
+// @flow
+
+import type { Player, AnimationSettings } from '../types';
+import BaseCanvas from './BaseCanvas';
+import { mergeOptions, easeFunctions } from '../utils';
+
+type Timeline = {
+ active: boolean;
+ initialized: boolean;
+ completed: boolean;
+ startValue: any;
+ byValue: any;
+ endValue: any;
+ ease?: Function;
+ onComplete?: Function;
+ keyPoint: number;
+ duration: number;
+ beginTime: number;
+ endTime: number;
+ from?: any;
+ to: any;
+}
+
+class Animation {
+ _player: Player;
+ _options: {
+ animation: AnimationSettings[];
+ canvas: BaseCanvas
+ };
+ _canvas: BaseCanvas;
+ _timeline: Timeline[];
+ _active: boolean;
+
+ constructor(player: Player, options: {animation: AnimationSettings[], canvas: BaseCanvas}){
+ this._player = player;
+ this._options = mergeOptions({}, this._options);
+ this._options = mergeOptions(this._options, options);
+
+ this._canvas = this._options.canvas;
+ this._timeline = [];
+
+ this._options.animation.forEach((obj: AnimationSettings) =>{
+ this.addTimeline(obj);
+ });
+ }
+
+ addTimeline(opt: AnimationSettings){
+ let timeline: Timeline = {
+ active: false,
+ initialized: false,
+ completed: false,
+ startValue: {},
+ byValue: {},
+ endValue: {},
+ keyPoint: opt.keyPoint,
+ duration: opt.duration,
+ beginTime: Infinity,
+ endTime: Infinity,
+ onComplete: opt.onComplete,
+ from: opt.from,
+ to: opt.to
+ };
+
+ if(typeof opt.ease === "string"){
+ timeline.ease = easeFunctions[opt.ease];
+ }
+ if(typeof opt.ease === "undefined"){
+ timeline.ease = easeFunctions.linear;
+ }
+
+ this._timeline.push(timeline);
+ this.attachEvents();
+ }
+
+ initialTimeline(timeline: Timeline){
+ for(let key in timeline.to){
+ if(timeline.to.hasOwnProperty(key)){
+ let from = timeline.from? (typeof timeline.from[key] !== "undefined"? timeline.from[key] : this._canvas[`_${key}`]) : this._canvas[`_${key}`];
+ timeline.startValue[key] = from;
+ timeline.endValue[key] = timeline.to[key];
+ timeline.byValue[key] = timeline.to[key] - from;
+ }
+ }
+ }
+
+ processTimeline(timeline: Timeline, animationTime: number){
+ for (let key in timeline.to){
+ if (timeline.to.hasOwnProperty(key)) {
+ let newVal = timeline.ease && timeline.ease(animationTime, timeline.startValue[key], timeline.byValue[key], timeline.duration);
+ if(key === "fov"){
+ this._canvas._camera.fov = newVal;
+ this._canvas._camera.updateProjectionMatrix();
+ }else{
+ this._canvas[`_${key}`] = newVal;
+ }
+ }
+ }
+ }
+
+ attachEvents(){
+ this._active = true;
+ this._canvas.addListener("beforeRender", this.renderAnimation.bind(this));
+ this._player.on("seeked", this.handleVideoSeek.bind(this));
+ }
+
+ detachEvents(){
+ this._active = false;
+ this._canvas.controlable = true;
+ this._canvas.removeListener("beforeRender", this.renderAnimation.bind(this));
+ }
+
+ handleVideoSeek(){
+ let currentTime = this._player.getVideoEl().currentTime * 1000;
+ let resetTimeline = 0;
+ this._timeline.forEach((timeline: Timeline)=>{
+ let res = timeline.keyPoint >= currentTime || (timeline.keyPoint <= currentTime && (timeline.keyPoint + timeline.duration) >= currentTime);
+ if(res){
+ resetTimeline++;
+ timeline.completed = false;
+ timeline.initialized = false;
+ }
+ });
+
+ if(resetTimeline > 0 && !this._active){
+ this.attachEvents();
+ }
+ }
+
+ renderAnimation(){
+ let currentTime = this._player.getVideoEl().currentTime * 1000;
+ let completeTimeline = 0;
+ let inActiveTimeline = 0;
+ this._timeline.filter((timeline: Timeline)=>{
+ if(timeline.completed) {
+ completeTimeline++;
+ return false;
+ }
+ let res = timeline.keyPoint <= currentTime && (timeline.keyPoint + timeline.duration) > currentTime;
+ timeline.active = res;
+ if(timeline.active === false) inActiveTimeline++;
+
+ if(res && !timeline.initialized){
+ timeline.initialized = true;
+ timeline.beginTime = timeline.keyPoint;
+ timeline.endTime = timeline.beginTime + timeline.duration;
+ this.initialTimeline(timeline);
+ }
+ if(timeline.endTime <= currentTime){
+ timeline.completed = true;
+ this.processTimeline(timeline, timeline.duration);
+ if(timeline.onComplete){
+ timeline.onComplete.call(this);
+ }
+ }
+ return res;
+ }).forEach((timeline: Timeline)=>{
+ let animationTime = currentTime - timeline.beginTime;
+ this.processTimeline(timeline, animationTime);
+ });
+
+ this._canvas.controlable = inActiveTimeline === this._timeline.length;
+
+ if(completeTimeline === this._timeline.length){
+ this.detachEvents();
+ }
+ }
+}
+
+export default Animation;
\ No newline at end of file
diff --git a/src/scripts/Components/BaseCanvas.js b/src/scripts/Components/BaseCanvas.js
index 17cee34..0a33cb1 100644
--- a/src/scripts/Components/BaseCanvas.js
+++ b/src/scripts/Components/BaseCanvas.js
@@ -34,6 +34,7 @@ class BaseCanvas extends Component{
/**
* Interaction
*/
+ _controlable: boolean;
_VRMode: boolean;
_mouseDown: boolean;
_mouseDownPointer: Point;
@@ -68,6 +69,7 @@ class BaseCanvas extends Component{
this._isUserInteracting = false;
this._runOnMobile = mobileAndTabletcheck();
this._VRMode = false;
+ this._controlable = true;
this._mouseDownPointer = {
x: 0,
@@ -363,24 +365,27 @@ class BaseCanvas extends Component{
}
render(){
- if(!this._isUserInteracting){
- let symbolLat = (this._lat > this.options.initLat)? -1 : 1;
- let symbolLon = (this._lon > this.options.initLon)? -1 : 1;
- if(this.options.backToInitLat){
- this._lat = (
- this._lat > (this.options.initLat - Math.abs(this.options.returnLatSpeed)) &&
- this._lat < (this.options.initLat + Math.abs(this.options.returnLatSpeed))
- )? this.options.initLat : this._lat + this.options.returnLatSpeed * symbolLat;
- }
- if(this.options.backToInitLon){
- this._lon = (
- this._lon > (this.options.initLon - Math.abs(this.options.returnLonSpeed)) &&
- this._lon < (this.options.initLon + Math.abs(this.options.returnLonSpeed))
- )? this.options.initLon : this._lon + this.options.returnLonSpeed * symbolLon;
+ this.trigger("beforeRender");
+ if(this._controlable){
+ if(!this._isUserInteracting){
+ let symbolLat = (this._lat > this.options.initLat)? -1 : 1;
+ let symbolLon = (this._lon > this.options.initLon)? -1 : 1;
+ if(this.options.backToInitLat){
+ this._lat = (
+ this._lat > (this.options.initLat - Math.abs(this.options.returnLatSpeed)) &&
+ this._lat < (this.options.initLat + Math.abs(this.options.returnLatSpeed))
+ )? this.options.initLat : this._lat + this.options.returnLatSpeed * symbolLat;
+ }
+ if(this.options.backToInitLon){
+ this._lon = (
+ this._lon > (this.options.initLon - Math.abs(this.options.returnLonSpeed)) &&
+ this._lon < (this.options.initLon + Math.abs(this.options.returnLonSpeed))
+ )? this.options.initLon : this._lon + this.options.returnLonSpeed * symbolLon;
+ }
+ }else if(this._accelector.x !== 0 && this._accelector.y !== 0){
+ this._lat += this._accelector.y;
+ this._lon += this._accelector.x;
}
- }else if(this._accelector.x !== 0 && this._accelector.y !== 0){
- this._lat += this._accelector.y;
- this._lon += this._accelector.x;
}
if(this._options.minLon === 0 && this._options.maxLon === 360){
@@ -406,6 +411,14 @@ class BaseCanvas extends Component{
get VRMode(): boolean{
return this._VRMode;
}
+
+ get controlable(): boolean{
+ return this._controlable;
+ }
+
+ set controlable(val: boolean): void{
+ this._controlable = val;
+ }
}
export default BaseCanvas;
\ No newline at end of file
diff --git a/src/scripts/Components/Marker.js b/src/scripts/Components/Marker.js
index a756334..b53aba1 100644
--- a/src/scripts/Components/Marker.js
+++ b/src/scripts/Components/Marker.js
@@ -50,11 +50,17 @@ class Marker extends Component{
enableMarker(){
this._enable = true;
this.addClass("vjs-marker--enable");
+ if(this.options.onShow){
+ this.options.onShow.call(null);
+ }
}
disableMarker(){
this._enable = false;
this.removeClass("vjs-marker--enable");
+ if(this.options.onHide){
+ this.options.onHide.call(null);
+ }
}
render(canvas: BaseCanvas, camera: THREE.PerspectiveCamera){
diff --git a/src/scripts/Components/MarkerContainer.js b/src/scripts/Components/MarkerContainer.js
index bdf4a1d..3d9e87f 100644
--- a/src/scripts/Components/MarkerContainer.js
+++ b/src/scripts/Components/MarkerContainer.js
@@ -3,6 +3,7 @@
import BaseCanvas from './BaseCanvas';
import Component from './Component';
import MarkerGroup from './MarkerGroup';
+import { mergeOptions } from '../utils';
import type { Player, MarkerSettings } from '../types';
class MarkerContainer extends Component{
@@ -24,10 +25,17 @@ class MarkerContainer extends Component{
markers: this.options.markers,
camera: this._canvas._camera
});
+
+ let markersSettings = this.options.markers.map((marker: MarkerSettings)=>{
+ let newMarker = mergeOptions({}, marker);
+ newMarker.onShow = undefined;
+ newMarker.onHide = undefined;
+ return newMarker;
+ });
let rightMarkerGroup = new MarkerGroup(this.player, {
id: "right_group",
canvas: this._canvas,
- markers: this.options.markers,
+ markers: markersSettings,
camera: this._canvas._camera
});
this.addChild("leftMarkerGroup", leftMarkerGroup);
diff --git a/src/scripts/Panorama.js b/src/scripts/Panorama.js
index db6e76f..0712f80 100644
--- a/src/scripts/Panorama.js
+++ b/src/scripts/Panorama.js
@@ -1,7 +1,7 @@
// @flow
import makeVideoPlayableInline from 'iphone-inline-video';
-import type {Settings, Player, VideoTypes, Coordinates} from './types/index';
+import type {Settings, Player, VideoTypes, Coordinates, AnimationSettings} from './types/index';
import type BaseCanvas from './Components/BaseCanvas';
import EventEmitter from 'wolfy87-eventemitter';
import Equirectangular from './Components/Equirectangular';
@@ -13,6 +13,7 @@ import Notification from './Components/Notification';
import Thumbnail from './Components/Thumbnail';
import VRButton from './Components/VRButton';
import MarkerContainer from './Components/MarkerContainer';
+import Animation from './Components/Animation';
import { Detector, webGLErrorMessage, crossDomainWarning, transitionEvent, mergeOptions, mobileAndTabletcheck, isIos, isRealIphone, warning } from './utils';
const runOnMobile = mobileAndTabletcheck();
@@ -97,7 +98,9 @@ export const defaults: Settings = {
HideTime: 3000,
},
- Markers: false
+ Markers: false,
+
+ Animations: false
};
export const VR180Defaults: any = {
@@ -122,6 +125,7 @@ class Panorama extends EventEmitter{
_player: Player;
_videoCanvas: BaseCanvas;
_thumbnailCanvas: BaseCanvas | null;
+ _animation: Animation;
/**
* check legacy option settings and produce warning message if user use legacy options, automatically set it to new options.
@@ -349,17 +353,34 @@ class Panorama extends EventEmitter{
this.player.addComponent("markerContainer", markerContainer);
}
+ //initial animations
+ if(this.options.Animation && Array.isArray(this.options.Animation)){
+ this._animation = new Animation(this.player, {
+ animation: this.options.Animation,
+ canvas: this.videoCanvas
+ });
+ }
+
//detect black screen
if(window.console && window.console.error){
let originalErrorFunction = window.console.error;
+ let originalWarnFunction = window.console.warn;
window.console.error = (error)=>{
if(error.message.indexOf("insecure") !== -1){
this.popupNotification(crossDomainWarning());
this.dispose();
}
};
+ window.console.warn = (warn) =>{
+ if(warn.indexOf("gl.getShaderInfoLog") !== -1){
+ this.popupNotification(crossDomainWarning());
+ this.dispose();
+ window.console.warn = originalWarnFunction;
+ }
+ };
setTimeout(()=>{
window.console.error = originalErrorFunction;
+ window.console.warn = originalWarnFunction;
}, 500);
}
};
@@ -405,6 +426,18 @@ class Panorama extends EventEmitter{
}
}
+ addTimeline(animation: AnimationSettings) : void{
+ this._animation.addTimeline(animation);
+ }
+
+ enableAnimation(){
+ this._animation.attachEvents();
+ }
+
+ disableAnimation(){
+ this._animation.detachEvents();
+ }
+
getCoordinates(): Coordinates{
let canvas = this.thumbnailCanvas || this.videoCanvas;
return {
diff --git a/src/scripts/types/Settings.js b/src/scripts/types/Settings.js
index c98d229..1d49712 100644
--- a/src/scripts/types/Settings.js
+++ b/src/scripts/types/Settings.js
@@ -26,9 +26,30 @@ export type MarkerSettings = {
duration?: number;
/**
- * callback function when marker is disappear
+ * callback function when marker is shown
*/
- complete?: Function;
+ onShow?: Function;
+ /**
+ * callback function when marker is hidden
+ */
+ onHide?: Function;
+}
+
+export type AnimationSettings = {
+ keyPoint: number;
+ from?: {
+ lon?: number;
+ lat?: number;
+ fov?: number;
+ };
+ to: {
+ lon?: number;
+ lat?: number;
+ fov?: number;
+ };
+ duration: number;
+ ease?: Function | string;
+ onComplete?: Function;
}
/**
@@ -136,7 +157,9 @@ export type Settings = {
HideTime?: number;
};
- Markers?: MarkerSettings[] | boolean,
+ Markers?: MarkerSettings[] | boolean;
+
+ Animation?: AnimationSettings[] | boolean;
ready?: Function;
diff --git a/src/scripts/utils/animation.js b/src/scripts/utils/animation.js
index c36a272..cda7c68 100644
--- a/src/scripts/utils/animation.js
+++ b/src/scripts/utils/animation.js
@@ -17,4 +17,33 @@ function whichTransitionEvent(){
}
}
-export const transitionEvent = whichTransitionEvent();
\ No newline at end of file
+export const transitionEvent = whichTransitionEvent();
+
+//adopt from http://gizma.com/easing/
+function linear(t: number, b: number, c: number, d: number): number{
+ return c*t/d + b;
+}
+
+function easeInQuad(t: number, b: number, c: number, d: number): number {
+ t /= d;
+ return c*t*t + b;
+}
+
+function easeOutQuad(t: number, b: number, c: number, d: number): number {
+ t /= d;
+ return -c * t*(t-2) + b;
+}
+
+function easeInOutQuad(t: number, b: number, c: number, d: number): number {
+ t /= d / 2;
+ if (t < 1) return c / 2 * t * t + b;
+ t--;
+ return -c / 2 * (t * (t - 2) - 1) + b;
+}
+
+export const easeFunctions = {
+ linear: linear,
+ easeInQuad: easeInQuad,
+ easeOutQuad: easeOutQuad,
+ easeInOutQuad: easeInOutQuad
+};
\ No newline at end of file
diff --git a/src/scripts/utils/warning.js b/src/scripts/utils/warning.js
index 244e565..64623d3 100644
--- a/src/scripts/utils/warning.js
+++ b/src/scripts/utils/warning.js
@@ -23,6 +23,7 @@ export const warning = (message: string): void => {
export const crossDomainWarning = (): HTMLElement => {
let element = document.createElement( 'div' );
+ element.className = "vjs-cross-domain-unsupport";
element.innerHTML = "Sorry, Your browser don't support cross domain.";
return element;
};
\ No newline at end of file
diff --git a/src/styles/plugin.scss b/src/styles/plugin.scss
index d217c20..d2d1d9f 100644
--- a/src/styles/plugin.scss
+++ b/src/styles/plugin.scss
@@ -77,7 +77,7 @@
}
}
- #webgl-error-message{
+ #webgl-error-message, .vjs-cross-domain-unsupport{
position: relative;
font-family: monospace;
font-size: 13px;