diff --git a/api/js/etemplate/CustomHtmlElements/pdf-player.ts b/api/js/etemplate/CustomHtmlElements/pdf-player.ts
new file mode 100644
index 0000000000..dac1ab4e91
--- /dev/null
+++ b/api/js/etemplate/CustomHtmlElements/pdf-player.ts
@@ -0,0 +1,400 @@
+/**
+ * EGroupware Custom Html Elements - pdf player Web Components
+ *
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @package etemplate
+ * @subpackage api
+ * @link https://www.egroupware.org
+ * @author Hadi Nategh
+ * @copyright EGroupware GmbH
+ */
+
+import pdfjs from "@bundled-es-modules/pdfjs-dist/build/pdf";
+
+/*
+ This web component allows to display and play pdf file like a video player widget/element. Its attributes and
+ methodes are mostley identical as video html. No controls attribute supported yet.
+*/
+pdfjs.GlobalWorkerOptions.workerSrc = 'node_modules/@bundled-es-modules/pdfjs-dist/build/pdf.worker.js';
+
+/**
+ *
+ */
+type PdfViewArray = {
+ pdf: any,
+ zoom: number,
+ currentPage: number
+};
+
+// Create a class for the element
+class pdf_player extends HTMLElement {
+
+ /**
+ * shadow dom container
+ * @private
+ */
+ private readonly _shadow = null;
+ /**
+ * wrapper container holds canvas
+ * @private
+ */
+ private readonly _wrapper : HTMLDivElement;
+ /**
+ * Canvas element to render pdf
+ * @private
+ */
+ private _canvas : HTMLCanvasElement;
+ /**
+ * Styling contianer
+ * @private
+ */
+ private readonly _style : HTMLStyleElement;
+ /**
+ * keeps duration time internally
+ * @private
+ */
+ private _duration : number = 0;
+ /**
+ * keeps currentTime internally
+ * @private
+ */
+ private _currentTime : number = 0;
+ /**
+ * Keeps playing state internally
+ * @private
+ */
+ private __playing: boolean = false;
+ /**
+ * keeps playing interval id
+ * @private
+ */
+ private __playingInterval : number = 0;
+ /**
+ * keeps play back rate
+ * @private
+ */
+ private _playBackRate : number = 1000;
+ /**
+ * keeps ended state of playing pdf
+ * @private
+ */
+ private _ended : boolean = false;
+ /**
+ * keep paused state
+ * @private
+ */
+ private _paused : boolean = false;
+ /**
+ * keeps pdf doc states
+ * @private
+ */
+ private __pdfViewState : PdfViewArray = {
+ pdf: null,
+ currentPage: 1,
+ zoom: 1
+ };
+
+ constructor() {
+
+ super();
+
+ // Create a shadow root
+ this._shadow = this.attachShadow({mode: 'open'});
+
+ // Create wrapper
+ this._wrapper = document.createElement('div');
+ this._wrapper.setAttribute('class','wrapper');
+
+ // Create some CSS to apply to the shadow dom
+ this._style = document.createElement('style');
+
+ this._style.textContent = '.wrapper {' +
+ 'width: 100%;' +
+ 'height: auto;' +
+ 'display: block;' +
+ '}'+
+ '.wrapper canvas {' +
+ 'width: 100%;'+
+ 'height: auto;'+
+ '}';
+
+ // attach to the shadow dom
+ this._shadow.appendChild(this._style);
+ this._shadow.appendChild(this._wrapper);
+ }
+
+ /**
+ * set observable attributes
+ * @return {string[]}
+ */
+ static get observedAttributes() {
+ return ['src', 'type'];
+ }
+
+ /**
+ * Gets called on observable attributes changes
+ * @param name attribute name
+ * @param _
+ * @param newVal new value
+ */
+ attributeChangedCallback(name, _, newVal) {
+ switch(name)
+ {
+ case 'src':
+ this.__buildPDFView(newVal);
+ break;
+ case 'type':
+ // do nothing
+ break;
+ }
+ }
+
+ /**
+ * init/update pdf tag
+ * @param _value
+ * @private
+ */
+ private __buildPDFView(_value)
+ {
+ this._canvas = document.createElement('canvas');
+ this._wrapper.appendChild(this._canvas);
+ let longTask = pdfjs.getDocument(_value);
+ longTask.promise.then((pdf) => {
+
+ this.__pdfViewState.pdf = pdf;
+ this._duration = this.__pdfViewState.pdf._pdfInfo.numPages;
+
+ // initiate the pdf file viewer for the first time after loading
+ this.__render(1).then(_ =>{
+ this.__pushEvent('loadedmetadata');
+ });
+ });
+ }
+
+ /**
+ * Render given page from pdf into the canvas container
+ *
+ * @param _page
+ * @private
+ */
+ private __render(_page)
+ {
+ if (!this.__pdfViewState.pdf) return;
+ let p = _page || this.__pdfViewState.currentPage;
+ let self = this;
+ return this.__pdfViewState.pdf.getPage(p).then((page) => {
+ let canvasContext = self._canvas.getContext('2d');
+ let viewport = page.getViewport({scale:self.__pdfViewState.zoom});
+ self._canvas.width = viewport.width;
+ self._canvas.height = viewport.height;
+ // don't ty to render if page is under proccess
+ if (typeof page.intentStates.display == 'undefined')
+ {
+ page.render({
+ canvasContext: canvasContext,
+ viewport: viewport
+ });
+ }
+ });
+ }
+
+ /**
+ * Creates event and dispatches it
+ * @param _name
+ */
+ private __pushEvent(_name: string)
+ {
+ let event = document.createEvent("Event");
+ event.initEvent(_name, true, true);
+ this.dispatchEvent(event);
+ }
+
+ /**************************** PUBLIC ATTRIBUTES & METHODES *************************************************/
+
+ /****************************** ATTRIBUTES **************************************/
+
+ /**
+ * set src
+ * @param _value
+ */
+ set src(_value)
+ {
+ this._wrapper.children.forEach(_ch=>{
+ _ch.remove();
+ });
+ this.__buildPDFView(_value);
+ }
+
+ /**
+ * get src
+ * @return string returns comma separated sources
+ */
+ get src ()
+ {
+ return this.src;
+ }
+
+ /**
+ * currentTime
+ * @param _time
+ */
+ set currentTime(_time : number)
+ {
+ let time = Math.floor(_time < 1 ? 1 : _time);
+
+ if (time>this._duration)
+ {
+ // set ended state to true as it's the last page of pdf
+ this._ended = true;
+ // don't go further because it's litterally the last page
+ return;
+ }
+
+ // set ended state to false as it's not the end of the pdf
+ this._ended = false;
+ this._currentTime = time;
+ this.__pdfViewState.currentPage = time;
+ this.__render(time);
+ }
+
+ /**
+ * get currentTime
+ * @return {number}
+ */
+ get currentTime()
+ {
+ return this._currentTime;
+ }
+
+ /**
+ * get duration time
+ */
+ get duration()
+ {
+ return this._duration;
+ }
+
+ /**
+ * get paused attribute
+ */
+ get paused()
+ {
+ return this._paused;
+ }
+
+ /**
+ * set muted attribute
+ * @param _value
+ */
+ set muted(_value: boolean)
+ {
+ return;
+ }
+
+ /**
+ * get muted attribute
+ */
+ get muted()
+ {
+ return true;
+ }
+
+ set ended (_value : boolean)
+ {
+ this._ended = _value;
+ }
+
+ /**
+ * get ended attribute
+ */
+ get ended()
+ {
+ return this._ended;
+ }
+
+ /**
+ * set playbackRate
+ * @param _value
+ */
+ set playbackRate(_value: number)
+ {
+ this._playBackRate = _value*1000;
+ }
+
+ /**
+ * get playbackRate
+ */
+ get playbackRate()
+ {
+ return this._playBackRate;
+ }
+
+ /**
+ * set volume
+ */
+ set volume(_value: number)
+ {
+ return;
+ }
+
+ /**
+ * get volume
+ */
+ get volume()
+ {
+ return 0;
+ }
+
+
+ /************************************************* METHODES ******************************************/
+
+ /**
+ * Play
+ */
+ play()
+ {
+ this.__playing = true;
+ let self = this;
+ return new Promise(function(_resolve, _reject){
+ self.__playingInterval = window.setInterval(_=>{
+ if (self.currentTime >= self._duration)
+ {
+ self.ended = true;
+ self.pause();
+ }
+ self.currentTime +=1;
+ self.__pushEvent('timeupdate');
+ }, self._playBackRate);
+ _resolve();
+ });
+ }
+
+ /**
+ * pause
+ */
+ pause()
+ {
+ this.__playing = false;
+ this._paused = true;
+ window.clearInterval(this.__playingInterval);
+ }
+
+ /**
+ * seek previous page
+ */
+ prevPage()
+ {
+ this.currentTime -=1;
+ }
+
+ /**
+ * seek next page
+ */
+ nextPage()
+ {
+ this.currentTime +=1;
+ }
+}
+
+// Define pdf-player element
+customElements.define('pdf-player', pdf_player);
\ No newline at end of file
diff --git a/api/js/etemplate/et2_widget_video.ts b/api/js/etemplate/et2_widget_video.ts
index a284773733..701ea2024d 100644
--- a/api/js/etemplate/et2_widget_video.ts
+++ b/api/js/etemplate/et2_widget_video.ts
@@ -20,6 +20,7 @@ import {ClassWithAttributes} from "./et2_core_inheritance";
import {WidgetConfig, et2_register_widget} from "./et2_core_widget";
import {et2_IDOMNode} from "./et2_core_interfaces";
import "./CustomHtmlElements/multi-video";
+import "./CustomHtmlElements/pdf-player";
/**
* This widget represents the HTML5 video tag with all its optional attributes
@@ -174,7 +175,8 @@ export class et2_video extends et2_baseWidget implements et2_IDOMNode
return;
}
//Create Video tag
- this.video = jQuery(document.createElement(this._isYoutube()?"div":(this.options.multi_src ? "multi-video" : "video")))
+ this.video = jQuery(document.createElement(this._isYoutube() ? "div" :
+ (_type.match('pdf') ? "pdf-player" : (this.options.multi_src ? 'multi-video' : 'video' ))))
.addClass('et2_video')
.attr('id', this.dom_id);
diff --git a/package-lock.json b/package-lock.json
index ac0d09e13f..5a4f6b5ae5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"license": "GPL-2.0",
"dependencies": {
"@andxor/jquery-ui-touch-punch-fix": "^1.0.2",
+ "@bundled-es-modules/pdfjs-dist": "^2.5.207-rc1",
"@lion/button": "^0.14.2",
"@lion/core": "^0.18.2",
"@lion/input": "^0.15.4",
@@ -533,6 +534,11 @@
"resolved": "https://registry.npmjs.org/@bundled-es-modules/message-format/-/message-format-6.0.4.tgz",
"integrity": "sha512-NGUoPxqsBzDwvRhY3A3L/AhS1hzS9OWappfyDOyCwE7G3W4ua28gau7QwvJz7QzA6ArbAdeb8c1mLjvd1WUFAA=="
},
+ "node_modules/@bundled-es-modules/pdfjs-dist": {
+ "version": "2.5.207-rc1",
+ "resolved": "https://registry.npmjs.org/@bundled-es-modules/pdfjs-dist/-/pdfjs-dist-2.5.207-rc1.tgz",
+ "integrity": "sha512-e/UVP1g6dwjQLnu4MPf/mlESCIvyr/KgpoMUyxGcv4evCIuJwKR/fcfhG3p1NYo+49gJsd0hL2yz9kzhkCZ32A=="
+ },
"node_modules/@esm-bundle/chai": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/@esm-bundle/chai/-/chai-4.3.4.tgz",
@@ -6480,6 +6486,11 @@
"resolved": "https://registry.npmjs.org/@bundled-es-modules/message-format/-/message-format-6.0.4.tgz",
"integrity": "sha512-NGUoPxqsBzDwvRhY3A3L/AhS1hzS9OWappfyDOyCwE7G3W4ua28gau7QwvJz7QzA6ArbAdeb8c1mLjvd1WUFAA=="
},
+ "@bundled-es-modules/pdfjs-dist": {
+ "version": "2.5.207-rc1",
+ "resolved": "https://registry.npmjs.org/@bundled-es-modules/pdfjs-dist/-/pdfjs-dist-2.5.207-rc1.tgz",
+ "integrity": "sha512-e/UVP1g6dwjQLnu4MPf/mlESCIvyr/KgpoMUyxGcv4evCIuJwKR/fcfhG3p1NYo+49gJsd0hL2yz9kzhkCZ32A=="
+ },
"@esm-bundle/chai": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/@esm-bundle/chai/-/chai-4.3.4.tgz",
diff --git a/package.json b/package.json
index c5d14aeeae..1cd7c3011d 100644
--- a/package.json
+++ b/package.json
@@ -54,6 +54,7 @@
},
"dependencies": {
"@andxor/jquery-ui-touch-punch-fix": "^1.0.2",
+ "@bundled-es-modules/pdfjs-dist": "^2.5.207-rc1",
"@lion/button": "^0.14.2",
"@lion/core": "^0.18.2",
"@lion/input": "^0.15.4",