forked from EVOgeek/Vmeda.Online
commit
138377e504
|
@ -3618,7 +3618,6 @@
|
|||
"version": "7.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz",
|
||||
"integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
|
@ -9255,6 +9254,71 @@
|
|||
"eslint-visitor-keys": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@videojs/http-streaming": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.15.1.tgz",
|
||||
"integrity": "sha512-/uuN3bVkEeJAdrhu5Hyb19JoUo3CMys7yf2C1vUjeL1wQaZ4Oe8JrZzRrnWZ0rjvPgKfNLPXQomsRtgrMoRMJQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "3.0.5",
|
||||
"aes-decrypter": "3.1.3",
|
||||
"global": "^4.4.0",
|
||||
"m3u8-parser": "4.8.0",
|
||||
"mpd-parser": "^0.22.1",
|
||||
"mux.js": "6.0.1",
|
||||
"video.js": "^6 || ^7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
|
||||
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@videojs/vhs-utils": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz",
|
||||
"integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"global": "^4.4.0",
|
||||
"url-toolkit": "^2.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
|
||||
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@videojs/xhr": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.6.0.tgz",
|
||||
"integrity": "sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"global": "~4.4.0",
|
||||
"is-function": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"@webassemblyjs/ast": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
|
||||
|
@ -9430,6 +9494,11 @@
|
|||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"@xmldom/xmldom": {
|
||||
"version": "0.8.6",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.6.tgz",
|
||||
"integrity": "sha512-uRjjusqpoqfmRkTaNuLJ2VohVr67Q5YwDATW3VU7PfzTj6IRaihGrYI7zckGZjxQPBIp63nfvJbM+Yu5ICh0Bg=="
|
||||
},
|
||||
"@xtuc/ieee754": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
||||
|
@ -9567,6 +9636,32 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"aes-decrypter": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.3.tgz",
|
||||
"integrity": "sha512-VkG9g4BbhMBy+N5/XodDeV6F02chEk9IpgRTq/0bS80y4dzy79VH2Gtms02VXomf3HmyRe3yyJYkJ990ns+d6A==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^3.0.5",
|
||||
"global": "^4.4.0",
|
||||
"pkcs7": "^1.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
|
||||
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
|
||||
|
@ -15674,8 +15769,7 @@
|
|||
"dom-walk": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
|
||||
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
|
||||
},
|
||||
"domain-browser": {
|
||||
"version": "1.2.0",
|
||||
|
@ -18627,7 +18721,6 @@
|
|||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
|
||||
"integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"min-document": "^2.19.0",
|
||||
"process": "^0.11.10"
|
||||
|
@ -20030,6 +20123,11 @@
|
|||
"integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=",
|
||||
"dev": true
|
||||
},
|
||||
"individual": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz",
|
||||
"integrity": "sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g=="
|
||||
},
|
||||
"infer-owner": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
|
||||
|
@ -20545,8 +20643,7 @@
|
|||
"is-function": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz",
|
||||
"integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ=="
|
||||
},
|
||||
"is-generator-fn": {
|
||||
"version": "2.1.0",
|
||||
|
@ -22371,6 +22468,11 @@
|
|||
"source-map-support": "^0.5.5"
|
||||
}
|
||||
},
|
||||
"keycode": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.1.tgz",
|
||||
"integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg=="
|
||||
},
|
||||
"keytar": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/keytar/-/keytar-7.2.0.tgz",
|
||||
|
@ -22940,6 +23042,31 @@
|
|||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"m3u8-parser": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.8.0.tgz",
|
||||
"integrity": "sha512-UqA2a/Pw3liR6Df3gwxrqghCP17OpPlQj6RBPLYygf/ZSQ4MoSgvdvhvt35qV+3NaaA0FSZx93Ix+2brT1U7cA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^3.0.5",
|
||||
"global": "^4.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
|
||||
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"macos-release": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.0.tgz",
|
||||
|
@ -23425,7 +23552,6 @@
|
|||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
|
||||
"integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dom-walk": "^0.1.0"
|
||||
}
|
||||
|
@ -23708,6 +23834,32 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"mpd-parser": {
|
||||
"version": "0.22.1",
|
||||
"resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.22.1.tgz",
|
||||
"integrity": "sha512-fwBebvpyPUU8bOzvhX0VQZgSohncbgYwUyJJoTSNpmy7ccD2ryiCvM7oRkn/xQH5cv73/xU7rJSNCLjdGFor0Q==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^3.0.5",
|
||||
"@xmldom/xmldom": "^0.8.3",
|
||||
"global": "^4.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
|
||||
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
|
@ -23740,6 +23892,30 @@
|
|||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
||||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
|
||||
},
|
||||
"mux.js": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mux.js/-/mux.js-6.0.1.tgz",
|
||||
"integrity": "sha512-22CHb59rH8pWGcPGW5Og7JngJ9s+z4XuSlYvnxhLuc58cA1WqGDQPzuG8I+sPm1/p0CdgpzVTaKW408k5DNn8w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"global": "^4.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
|
||||
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.14.1",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
|
||||
|
@ -24875,6 +25051,29 @@
|
|||
"integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
|
||||
"dev": true
|
||||
},
|
||||
"ogv": {
|
||||
"version": "1.8.9",
|
||||
"resolved": "https://registry.npmjs.org/ogv/-/ogv-1.8.9.tgz",
|
||||
"integrity": "sha512-tQA2E3E2PzdWqxIaI5X8q8Vxvj1Ap3JSZmD1MfnA+cTY3o0t+06zY4RKXckQ9pxeqGy/UH4l4QensssmbPLwAQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
|
||||
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
|
@ -25905,6 +26104,14 @@
|
|||
"node-modules-regexp": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"pkcs7": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz",
|
||||
"integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.5.5"
|
||||
}
|
||||
},
|
||||
"pkg-dir": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
|
||||
|
@ -26858,8 +27065,7 @@
|
|||
"process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
"integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
|
||||
"dev": true
|
||||
"integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
|
@ -28270,8 +28476,7 @@
|
|||
"regenerator-runtime": {
|
||||
"version": "0.13.5",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
|
||||
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
|
||||
},
|
||||
"regenerator-transform": {
|
||||
"version": "0.14.5",
|
||||
|
@ -28944,6 +29149,14 @@
|
|||
"aproba": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"rust-result": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz",
|
||||
"integrity": "sha512-6cJzSBU+J/RJCF063onnQf0cDUOHs9uZI1oroSGnHOph+CQTIJ5Pp2hK5kEQq1+7yE/EEWfulSNXAQ2jikPthA==",
|
||||
"requires": {
|
||||
"individual": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"rxjs": {
|
||||
"version": "6.5.5",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz",
|
||||
|
@ -28964,6 +29177,14 @@
|
|||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safe-json-parse": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-4.0.0.tgz",
|
||||
"integrity": "sha512-RjZPPHugjK0TOzFrLZ8inw44s9bKox99/0AZW9o/BEQVrJfhI+fIHMErnPyRa89/yRXUUr93q+tiN6zhoVV4wQ==",
|
||||
"requires": {
|
||||
"rust-result": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"safe-regex": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
|
||||
|
@ -32936,6 +33157,11 @@
|
|||
"prepend-http": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"url-toolkit": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz",
|
||||
"integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg=="
|
||||
},
|
||||
"use": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
|
||||
|
@ -33118,6 +33344,54 @@
|
|||
"extsprintf": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"video.js": {
|
||||
"version": "7.21.1",
|
||||
"resolved": "https://registry.npmjs.org/video.js/-/video.js-7.21.1.tgz",
|
||||
"integrity": "sha512-AvHfr14ePDHCfW5Lx35BvXk7oIonxF6VGhSxocmTyqotkQpxwYdmt4tnQSV7MYzNrYHb0GI8tJMt20NDkCQrxg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/http-streaming": "2.15.1",
|
||||
"@videojs/vhs-utils": "^3.0.4",
|
||||
"@videojs/xhr": "2.6.0",
|
||||
"aes-decrypter": "3.1.3",
|
||||
"global": "^4.4.0",
|
||||
"keycode": "^2.2.0",
|
||||
"m3u8-parser": "4.8.0",
|
||||
"mpd-parser": "0.22.1",
|
||||
"mux.js": "6.0.1",
|
||||
"safe-json-parse": "4.0.0",
|
||||
"videojs-font": "3.2.0",
|
||||
"videojs-vtt.js": "^0.15.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
|
||||
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"videojs-font": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-3.2.0.tgz",
|
||||
"integrity": "sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA=="
|
||||
},
|
||||
"videojs-vtt.js": {
|
||||
"version": "0.15.4",
|
||||
"resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.4.tgz",
|
||||
"integrity": "sha512-r6IhM325fcLb1D6pgsMkTQT1PpFdUdYZa1iqk7wJEu+QlibBwATPfPc9Bg8Jiym0GE5yP1AG2rMLu+QMVWkYtA==",
|
||||
"requires": {
|
||||
"global": "^4.3.1"
|
||||
}
|
||||
},
|
||||
"vinyl": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz",
|
||||
|
|
|
@ -123,9 +123,11 @@
|
|||
"moment": "2.29.4",
|
||||
"moment-timezone": "0.5.38",
|
||||
"nl.kingsquare.cordova.background-audio": "1.0.1",
|
||||
"ogv": "1.8.9",
|
||||
"rxjs": "6.5.5",
|
||||
"ts-md5": "1.2.7",
|
||||
"tslib": "2.3.1",
|
||||
"video.js": "7.21.1",
|
||||
"zone.js": "0.10.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -28,6 +28,8 @@ const ASSETS = {
|
|||
'/node_modules/mathjax/jax/output/PreviewHTML': '/lib/mathjax/jax/output/PreviewHTML',
|
||||
'/node_modules/mathjax/localization': '/lib/mathjax/localization',
|
||||
'/src/core/features/h5p/assets': '/lib/h5p',
|
||||
'/node_modules/ogv/dist': '/lib/ogv',
|
||||
'/node_modules/video.js/dist/video-js.min.css': '/lib/video.js/video-js.min.css',
|
||||
};
|
||||
|
||||
module.exports = function(ctx) {
|
||||
|
|
|
@ -0,0 +1,748 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { CorePlatform } from '@services/platform';
|
||||
import { OGVPlayer, OGVCompat, OGVLoader } from 'ogv';
|
||||
import videojs, { PreloadOption, TechSourceObject, VideoJSOptions } from 'video.js';
|
||||
|
||||
export const Tech = videojs.getComponent('Tech');
|
||||
|
||||
/**
|
||||
* Object.defineProperty but "lazy", which means that the value is only set after
|
||||
* it retrieved the first time, rather than being set right away.
|
||||
*
|
||||
* @param obj The object to set the property on.
|
||||
* @param key The key for the property to set.
|
||||
* @param getValue The function used to get the value when it is needed.
|
||||
* @param setter Whether a setter should be allowed or not.
|
||||
* @returns Object.
|
||||
*/
|
||||
const defineLazyProperty = <T>(obj: T, key: string, getValue: () => unknown, setter = true): T => {
|
||||
const set = (value: unknown): void => {
|
||||
Object.defineProperty(obj, key, { value, enumerable: true, writable: true });
|
||||
};
|
||||
|
||||
const options: PropertyDescriptor = {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() {
|
||||
const value = getValue();
|
||||
|
||||
set(value);
|
||||
|
||||
return value;
|
||||
},
|
||||
};
|
||||
|
||||
if (setter) {
|
||||
options.set = set;
|
||||
}
|
||||
|
||||
return Object.defineProperty(obj, key, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* OgvJS Media Controller for VideoJS - Wrapper for ogv.js Media API.
|
||||
*
|
||||
* Code adapted from https://github.com/HuongNV13/videojs-ogvjs/blob/f9b12bd53018d967bb305f02725834a98f20f61f/src/plugin.js
|
||||
* Modified in the following ways:
|
||||
* - Adapted to Typescript.
|
||||
* - Use our own functions to detect the platform instead of using getDeviceOS.
|
||||
* - Add an initialize static function.
|
||||
* - In the play function, reset the media if it already ended to fix problems with replaying media.
|
||||
* - Allow full screen in iOS devices, and implement enterFullScreen and exitFullScreen to use a fake full screen.
|
||||
*/
|
||||
export class VideoJSOgvJS extends Tech {
|
||||
|
||||
/**
|
||||
* List of available events of the media player.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
static readonly Events = [
|
||||
'loadstart',
|
||||
'suspend',
|
||||
'abort',
|
||||
'error',
|
||||
'emptied',
|
||||
'stalled',
|
||||
'loadedmetadata',
|
||||
'loadeddata',
|
||||
'canplay',
|
||||
'canplaythrough',
|
||||
'playing',
|
||||
'waiting',
|
||||
'seeking',
|
||||
'seeked',
|
||||
'ended',
|
||||
'durationchange',
|
||||
'timeupdate',
|
||||
'progress',
|
||||
'play',
|
||||
'pause',
|
||||
'ratechange',
|
||||
'resize',
|
||||
'volumechange',
|
||||
];
|
||||
|
||||
protected playerId?: string;
|
||||
protected parentElement: HTMLElement | null = null;
|
||||
protected placeholderElement = document.createElement('div');
|
||||
|
||||
// Variables/functions defined in parent classes.
|
||||
protected el_!: OGVPlayerEl; // eslint-disable-line @typescript-eslint/naming-convention
|
||||
protected options_!: VideoJSOptions; // eslint-disable-line @typescript-eslint/naming-convention
|
||||
protected currentSource_?: TechSourceObject; // eslint-disable-line @typescript-eslint/naming-convention
|
||||
protected triggerReady!: () => void;
|
||||
protected on!: (name: string, callback: (e?: Event) => void) => void;
|
||||
|
||||
/**
|
||||
* Create an instance of this Tech.
|
||||
*
|
||||
* @param options The key/value store of player options.
|
||||
* @param ready Callback function to call when the `OgvJS` Tech is ready.
|
||||
*/
|
||||
constructor(options: VideoJSTechOptions, ready: () => void) {
|
||||
super(options, ready);
|
||||
|
||||
this.el_.src = options.src || options.source?.src || options.sources?.[0]?.src || this.el_.src;
|
||||
VideoJSOgvJS.setIfAvailable(this.el_, 'autoplay', options.autoplay);
|
||||
VideoJSOgvJS.setIfAvailable(this.el_, 'loop', options.loop);
|
||||
VideoJSOgvJS.setIfAvailable(this.el_, 'poster', options.poster);
|
||||
VideoJSOgvJS.setIfAvailable(this.el_, 'preload', options.preload);
|
||||
this.playerId = options.playerId;
|
||||
|
||||
this.on('loadedmetadata', () => {
|
||||
if (CorePlatform.isIPhone()) {
|
||||
// iPhoneOS add some inline styles to the canvas, we need to remove it.
|
||||
const canvas = this.el_.getElementsByTagName('canvas')[0];
|
||||
|
||||
canvas.style.removeProperty('width');
|
||||
canvas.style.removeProperty('margin');
|
||||
}
|
||||
|
||||
this.triggerReady();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value for the player is it has that property.
|
||||
*
|
||||
* @param el HTML player.
|
||||
* @param name Name of the property.
|
||||
* @param value Value to set.
|
||||
*/
|
||||
static setIfAvailable(el: HTMLElement, name: string, value: unknown): void {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (el.hasOwnProperty(name)) {
|
||||
el[name] = value;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if browser/device is supported by Ogv.JS.
|
||||
*
|
||||
* @returns Whether it's supported.
|
||||
*/
|
||||
static isSupported(): boolean {
|
||||
return OGVCompat.supported('OGVPlayer');
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the tech can support the given type.
|
||||
*
|
||||
* @param type The mimetype to check.
|
||||
* @returns 'probably', 'maybe', or '' (empty string).
|
||||
*/
|
||||
static canPlayType(type: string): string {
|
||||
return (type.indexOf('/ogg') !== -1 || type.indexOf('/webm')) ? 'maybe' : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the tech can support the given source.
|
||||
*
|
||||
* @param srcObj The source object.
|
||||
* @returns The options passed to the tech.
|
||||
*/
|
||||
static canPlaySource(srcObj: TechSourceObject): string {
|
||||
return VideoJSOgvJS.canPlayType(srcObj.type);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the volume can be changed in this browser/device.
|
||||
* Volume cannot be changed in a lot of mobile devices.
|
||||
* Specifically, it can't be changed from 1 on iOS.
|
||||
*
|
||||
* @returns True if volume can be controlled.
|
||||
*/
|
||||
static canControlVolume(): boolean {
|
||||
if (CorePlatform.isIPhone()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const player = new OGVPlayer();
|
||||
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
return player.hasOwnProperty('volume');
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the volume can be muted in this browser/device.
|
||||
*
|
||||
* @returns True if volume can be muted.
|
||||
*/
|
||||
static canMuteVolume(): boolean {
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the playback rate can be changed in this browser/device.
|
||||
*
|
||||
* @returns True if playback rate can be controlled.
|
||||
*/
|
||||
static canControlPlaybackRate(): boolean {
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check to see if native 'TextTracks' are supported by this browser/device.
|
||||
*
|
||||
* @returns True if native 'TextTracks' are supported.
|
||||
*/
|
||||
static supportsNativeTextTracks(): boolean {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the fullscreen resize is supported by this browser/device.
|
||||
*
|
||||
* @returns True if the fullscreen resize is supported.
|
||||
*/
|
||||
static supportsFullscreenResize(): boolean {
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the progress events is supported by this browser/device.
|
||||
*
|
||||
* @returns True if the progress events is supported.
|
||||
*/
|
||||
static supportsProgressEvents(): boolean {
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the time update events is supported by this browser/device.
|
||||
*
|
||||
* @returns True if the time update events is supported.
|
||||
*/
|
||||
static supportsTimeupdateEvents(): boolean {
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the 'OgvJS' Tech's DOM element.
|
||||
*
|
||||
* @returns The element that gets created.
|
||||
*/
|
||||
createEl(): OGVPlayerEl {
|
||||
const options = this.options_;
|
||||
|
||||
if (options.base) {
|
||||
OGVLoader.base = options.base;
|
||||
} else if (!OGVLoader.base) {
|
||||
throw new Error('Please specify the base for the ogv.js library');
|
||||
}
|
||||
|
||||
const el = new OGVPlayer(options);
|
||||
|
||||
el.className += ' vjs-tech';
|
||||
options.tag = el;
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start playback.
|
||||
*/
|
||||
play(): void {
|
||||
if (this.ended()) {
|
||||
// Reset the player, otherwise the Replay button doesn't work.
|
||||
this.el_.stop();
|
||||
}
|
||||
|
||||
this.el_.play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current playback speed.
|
||||
*
|
||||
* @returns Playback speed.
|
||||
*/
|
||||
playbackRate(): number {
|
||||
return this.el_.playbackRate || 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the playback speed.
|
||||
*
|
||||
* @param val Speed for the player to play.
|
||||
*/
|
||||
setPlaybackRate(val: number): void {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (this.el_.hasOwnProperty('playbackRate')) {
|
||||
this.el_.playbackRate = val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a TimeRanges object that represents the ranges of the media resource that the user agent has played.
|
||||
*
|
||||
* @returns The range of points on the media timeline that has been reached through normal playback.
|
||||
*/
|
||||
played(): TimeRanges {
|
||||
return this.el_.played;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause playback.
|
||||
*/
|
||||
pause(): void {
|
||||
this.el_.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the player paused or not.
|
||||
*
|
||||
* @returns Whether is paused.
|
||||
*/
|
||||
paused(): boolean {
|
||||
return this.el_.paused;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current playing time.
|
||||
*
|
||||
* @returns Current time.
|
||||
*/
|
||||
currentTime(): number {
|
||||
return this.el_.currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current playing time.
|
||||
*
|
||||
* @param seconds Current time of audio/video.
|
||||
*/
|
||||
setCurrentTime(seconds: number): void {
|
||||
try {
|
||||
this.el_.currentTime = seconds;
|
||||
} catch (e) {
|
||||
videojs.log(e, 'Media is not ready. (Video.JS)');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get media's duration.
|
||||
*
|
||||
* @returns Duration.
|
||||
*/
|
||||
duration(): number {
|
||||
if (this.el_.duration && this.el_.duration !== Infinity) {
|
||||
return this.el_.duration;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a TimeRange object that represents the intersection
|
||||
* of the time ranges for which the user agent has all
|
||||
* relevant media.
|
||||
*
|
||||
* @returns Time ranges.
|
||||
*/
|
||||
buffered(): TimeRanges {
|
||||
return this.el_.buffered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current volume level.
|
||||
*
|
||||
* @returns Volume.
|
||||
*/
|
||||
volume(): number {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
return this.el_.hasOwnProperty('volume') ? this.el_.volume : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current playing volume level.
|
||||
*
|
||||
* @param percentAsDecimal Volume percent as a decimal.
|
||||
*/
|
||||
setVolume(percentAsDecimal: number): void {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (!CorePlatform.isIPhone() && this.el_.hasOwnProperty('volume')) {
|
||||
this.el_.volume = percentAsDecimal;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the player muted or not.
|
||||
*
|
||||
* @returns Whether it's muted.
|
||||
*/
|
||||
muted(): boolean {
|
||||
return this.el_.muted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mute the player.
|
||||
*
|
||||
* @param muted True to mute the player.
|
||||
*/
|
||||
setMuted(muted: boolean): void {
|
||||
this.el_.muted = !!muted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the player muted by default or not.
|
||||
*
|
||||
* @returns Whether it's muted by default.
|
||||
*/
|
||||
defaultMuted(): boolean {
|
||||
return this.el_.defaultMuted || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the player width.
|
||||
*
|
||||
* @returns Width.
|
||||
*/
|
||||
width(): number {
|
||||
return this.el_.offsetWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the player height.
|
||||
*
|
||||
* @returns Height.
|
||||
*/
|
||||
height(): number {
|
||||
return this.el_.offsetHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the video width.
|
||||
*
|
||||
* @returns Video width.
|
||||
*/
|
||||
videoWidth(): number {
|
||||
return (<HTMLVideoElement> this.el_).videoWidth ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the video height.
|
||||
*
|
||||
* @returns Video heigth.
|
||||
*/
|
||||
videoHeight(): number {
|
||||
return (<HTMLVideoElement> this.el_).videoHeight ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set media source.
|
||||
*
|
||||
* @param src Source.
|
||||
* @returns Source when getting it, undefined when setting it.
|
||||
*/
|
||||
src(src?: string): string | undefined {
|
||||
if (typeof src === 'undefined') {
|
||||
return this.el_.src;
|
||||
}
|
||||
|
||||
this.el_.src = src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the media into the player.
|
||||
*/
|
||||
load(): void {
|
||||
this.el_.load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current media source.
|
||||
*
|
||||
* @returns Current source.
|
||||
*/
|
||||
currentSrc(): string {
|
||||
if (this.currentSource_) {
|
||||
return this.currentSource_.src;
|
||||
}
|
||||
|
||||
return this.el_.currentSrc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get media poster URL.
|
||||
*
|
||||
* @returns Poster.
|
||||
*/
|
||||
poster(): string {
|
||||
return 'poster' in this.el_ ? this.el_.poster : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set media poster URL.
|
||||
*
|
||||
* @param url The poster image's url.
|
||||
*/
|
||||
setPoster(url: string): void {
|
||||
(<HTMLVideoElement> this.el_).poster = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the media preloaded or not.
|
||||
*
|
||||
* @returns Whether it's preloaded.
|
||||
*/
|
||||
preload(): PreloadOption {
|
||||
return <PreloadOption> this.el_.preload || 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the media preload method.
|
||||
*
|
||||
* @param val Value for preload attribute.
|
||||
*/
|
||||
setPreload(val: PreloadOption): void {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (this.el_.hasOwnProperty('preload')) {
|
||||
this.el_.preload = val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the media auto-played or not.
|
||||
*
|
||||
* @returns Whether it's auto-played.
|
||||
*/
|
||||
autoplay(): boolean {
|
||||
return this.el_.autoplay || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set media autoplay method.
|
||||
*
|
||||
* @param val Value for autoplay attribute.
|
||||
*/
|
||||
setAutoplay(val: boolean): void {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (this.el_.hasOwnProperty('autoplay')) {
|
||||
this.el_.autoplay = !!val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the media has controls or not.
|
||||
*
|
||||
* @returns Whether it has controls.
|
||||
*/
|
||||
controls(): boolean {
|
||||
return this.el_.controls || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the media controls method.
|
||||
*
|
||||
* @param val Value for controls attribute.
|
||||
*/
|
||||
setControls(val: boolean): void {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (this.el_.hasOwnProperty('controls')) {
|
||||
this.el_.controls = !!val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the media looped or not.
|
||||
*
|
||||
* @returns Whether it's looped.
|
||||
*/
|
||||
loop(): boolean {
|
||||
return this.el_.loop || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the media loop method.
|
||||
*
|
||||
* @param val Value for loop attribute.
|
||||
*/
|
||||
setLoop(val: boolean): void {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (this.el_.hasOwnProperty('loop')) {
|
||||
this.el_.loop = !!val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a TimeRanges object that represents the
|
||||
* ranges of the media resource to which it is possible
|
||||
* for the user agent to seek.
|
||||
*
|
||||
* @returns Time ranges.
|
||||
*/
|
||||
seekable(): TimeRanges {
|
||||
return this.el_.seekable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is player in the "seeking" state or not.
|
||||
*
|
||||
* @returns Whether is in the seeking state.
|
||||
*/
|
||||
seeking(): boolean {
|
||||
return this.el_.seeking;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the media ended or not.
|
||||
*
|
||||
* @returns Whether it's ended.
|
||||
*/
|
||||
ended(): boolean {
|
||||
return this.el_.ended;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current state of network activity
|
||||
* NETWORK_EMPTY (numeric value 0)
|
||||
* NETWORK_IDLE (numeric value 1)
|
||||
* NETWORK_LOADING (numeric value 2)
|
||||
* NETWORK_NO_SOURCE (numeric value 3)
|
||||
*
|
||||
* @returns Network state.
|
||||
*/
|
||||
networkState(): number {
|
||||
return this.el_.networkState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current state of the player.
|
||||
* HAVE_NOTHING (numeric value 0)
|
||||
* HAVE_METADATA (numeric value 1)
|
||||
* HAVE_CURRENT_DATA (numeric value 2)
|
||||
* HAVE_FUTURE_DATA (numeric value 3)
|
||||
* HAVE_ENOUGH_DATA (numeric value 4)
|
||||
*
|
||||
* @returns Ready state.
|
||||
*/
|
||||
readyState(): number {
|
||||
return this.el_.readyState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the player support native fullscreen mode or not. (Mobile devices)
|
||||
*
|
||||
* @returns Whether it supports full screen.
|
||||
*/
|
||||
supportsFullScreen(): boolean {
|
||||
return !!this.playerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get media player error.
|
||||
*
|
||||
* @returns Error.
|
||||
*/
|
||||
error(): MediaError | null {
|
||||
return this.el_.error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter full screen mode.
|
||||
*/
|
||||
enterFullScreen(): void {
|
||||
// Use a "fake" full screen mode, moving the player to a different place in DOM to be able to use full screen size.
|
||||
const player = videojs.getPlayer(this.playerId ?? '');
|
||||
if (!player) {
|
||||
return;
|
||||
}
|
||||
|
||||
const container = player.el();
|
||||
this.parentElement = container.parentElement;
|
||||
if (!this.parentElement) {
|
||||
// Shouldn't happen, it means the element is not in DOM. Do not support full screen in this case.
|
||||
return;
|
||||
}
|
||||
|
||||
this.parentElement.replaceChild(this.placeholderElement, container);
|
||||
document.body.appendChild(container);
|
||||
container.classList.add('vjs-ios-moodleapp-fs');
|
||||
|
||||
player.isFullscreen(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit full screen mode.
|
||||
*/
|
||||
exitFullScreen(): void {
|
||||
if (!this.parentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const player = videojs.getPlayer(this.playerId ?? '');
|
||||
if (!player) {
|
||||
return;
|
||||
}
|
||||
|
||||
const container = player.el();
|
||||
this.parentElement.replaceChild(container, this.placeholderElement);
|
||||
container.classList.remove('vjs-ios-moodleapp-fs');
|
||||
|
||||
player.isFullscreen(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[
|
||||
['featuresVolumeControl', 'canControlVolume'],
|
||||
['featuresMuteControl', 'canMuteVolume'],
|
||||
['featuresPlaybackRate', 'canControlPlaybackRate'],
|
||||
['featuresNativeTextTracks', 'supportsNativeTextTracks'],
|
||||
['featuresFullscreenResize', 'supportsFullscreenResize'],
|
||||
['featuresProgressEvents', 'supportsProgressEvents'],
|
||||
['featuresTimeupdateEvents', 'supportsTimeupdateEvents'],
|
||||
].forEach(([key, fn]) => {
|
||||
defineLazyProperty(VideoJSOgvJS.prototype, key, () => VideoJSOgvJS[fn](), true);
|
||||
});
|
||||
/**
|
||||
* Initialize the controller.
|
||||
*/
|
||||
export const initializeVideoJSOgvJS = (): void => {
|
||||
OGVLoader.base = 'assets/lib/ogv';
|
||||
Tech.registerTech('OgvJS', VideoJSOgvJS);
|
||||
};
|
||||
|
||||
type OGVPlayerEl = (HTMLAudioElement | HTMLVideoElement) & {
|
||||
stop: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* VideoJS Tech options. It includes some options added by VideoJS internally.
|
||||
*/
|
||||
type VideoJSTechOptions = VideoJSOptions & {
|
||||
playerId?: string;
|
||||
};
|
|
@ -15,6 +15,7 @@
|
|||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||
|
||||
import { CoreFilterDelegate } from '@features/filter/services/filter-delegate';
|
||||
import { initializeVideoJSOgvJS } from './classes/videojs-ogvjs';
|
||||
import { AddonFilterMediaPluginHandler } from './services/handlers/mediaplugin';
|
||||
|
||||
@NgModule({
|
||||
|
@ -26,7 +27,11 @@ import { AddonFilterMediaPluginHandler } from './services/handlers/mediaplugin';
|
|||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
useValue: () => CoreFilterDelegate.registerHandler(AddonFilterMediaPluginHandler.instance),
|
||||
useValue: () => {
|
||||
CoreFilterDelegate.registerHandler(AddonFilterMediaPluginHandler.instance);
|
||||
|
||||
initializeVideoJSOgvJS();
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
|
|
@ -13,11 +13,17 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreExternalContentDirective } from '@directives/external-content';
|
||||
|
||||
import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/default-filter';
|
||||
import { CoreLang } from '@services/lang';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreUrlUtils } from '@services/utils/url';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreMedia } from '@singletons/media';
|
||||
import videojs, { VideoJSOptions, VideoJSPlayer } from 'video.js';
|
||||
|
||||
/**
|
||||
* Handler to support the Multimedia filter.
|
||||
|
@ -41,25 +47,100 @@ export class AddonFilterMediaPluginHandlerService extends CoreFilterDefaultHandl
|
|||
const videos = Array.from(this.template.content.querySelectorAll('video'));
|
||||
|
||||
videos.forEach((video) => {
|
||||
this.treatVideoFilters(video);
|
||||
this.treatYoutubeVideos(video);
|
||||
});
|
||||
|
||||
return this.template.innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat video filters. Currently only treating youtube video using video JS.
|
||||
* @inheritdoc
|
||||
*/
|
||||
handleHtml(container: HTMLElement): void {
|
||||
const mediaElements = Array.from(container.querySelectorAll<HTMLVideoElement | HTMLAudioElement>('video, audio'));
|
||||
|
||||
mediaElements.forEach((mediaElement) => {
|
||||
if (CoreMedia.mediaUsesJavascriptPlayer(mediaElement)) {
|
||||
this.useVideoJS(mediaElement);
|
||||
} else {
|
||||
// Remove the VideoJS classes and data if present.
|
||||
mediaElement.classList.remove('video-js');
|
||||
mediaElement.removeAttribute('data-setup');
|
||||
mediaElement.removeAttribute('data-setup-lazy');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Use video JS in a certain video or audio.
|
||||
*
|
||||
* @param mediaElement Media element.
|
||||
*/
|
||||
protected async useVideoJS(mediaElement: HTMLVideoElement | HTMLAudioElement): Promise<void> {
|
||||
const lang = await CoreLang.getCurrentLanguage();
|
||||
|
||||
// Wait for external-content to finish in the element and its sources.
|
||||
await Promise.all([
|
||||
CoreDirectivesRegistry.waitDirectivesReady(mediaElement, undefined, CoreExternalContentDirective),
|
||||
CoreDirectivesRegistry.waitDirectivesReady(mediaElement, 'source', CoreExternalContentDirective),
|
||||
]);
|
||||
|
||||
const dataSetupString = mediaElement.getAttribute('data-setup') || mediaElement.getAttribute('data-setup-lazy') || '{}';
|
||||
const data = CoreTextUtils.parseJSON<VideoJSOptions>(dataSetupString, {});
|
||||
|
||||
const player = videojs(mediaElement, {
|
||||
controls: true,
|
||||
techOrder: ['OgvJS'],
|
||||
language: lang,
|
||||
controlBar: {
|
||||
pictureInPictureToggle: false,
|
||||
},
|
||||
aspectRatio: data.aspectRatio,
|
||||
}, () => {
|
||||
if (mediaElement.tagName === 'VIDEO') {
|
||||
this.fixVideoJSPlayerSize(player);
|
||||
}
|
||||
});
|
||||
|
||||
CoreEvents.trigger(CoreEvents.JS_PLAYER_CREATED, {
|
||||
id: mediaElement.id,
|
||||
element: mediaElement,
|
||||
player,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix VideoJS player size.
|
||||
* If video width is wider than available width, video is cut off. Fix the dimensions in this case.
|
||||
*
|
||||
* @param player Player instance.
|
||||
*/
|
||||
protected fixVideoJSPlayerSize(player: VideoJSPlayer): void {
|
||||
const videoWidth = player.videoWidth();
|
||||
const videoHeight = player.videoHeight();
|
||||
const playerDimensions = player.currentDimensions();
|
||||
if (!videoWidth || !videoHeight || !playerDimensions.width || videoWidth === playerDimensions.width) {
|
||||
return;
|
||||
}
|
||||
|
||||
const candidateHeight = playerDimensions.width * videoHeight / videoWidth;
|
||||
if (!playerDimensions.height || Math.abs(candidateHeight - playerDimensions.height) > 1) {
|
||||
player.dimension('height', candidateHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat Video JS Youtube video links and translate them to iframes.
|
||||
*
|
||||
* @param video Video element.
|
||||
*/
|
||||
protected treatVideoFilters(video: HTMLElement): void {
|
||||
// Treat Video JS Youtube video links and translate them to iframes.
|
||||
protected treatYoutubeVideos(video: HTMLElement): void {
|
||||
if (!video.classList.contains('video-js')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dataSetupString = video.getAttribute('data-setup') || video.getAttribute('data-setup-lazy') || '{}';
|
||||
const data = <VideoDataSetup> CoreTextUtils.parseJSON(dataSetupString, {});
|
||||
const data = CoreTextUtils.parseJSON<VideoJSOptions>(dataSetupString, {});
|
||||
const youtubeUrl = data.techOrder?.[0] == 'youtube' && CoreUrlUtils.getYoutubeEmbedUrl(data.sources?.[0]?.src);
|
||||
|
||||
if (!youtubeUrl) {
|
||||
|
@ -81,10 +162,3 @@ export class AddonFilterMediaPluginHandlerService extends CoreFilterDefaultHandl
|
|||
}
|
||||
|
||||
export const AddonFilterMediaPluginHandler = makeSingleton(AddonFilterMediaPluginHandlerService);
|
||||
|
||||
type VideoDataSetup = {
|
||||
techOrder?: string[];
|
||||
sources?: {
|
||||
src?: string;
|
||||
}[];
|
||||
};
|
||||
|
|
|
@ -18,8 +18,8 @@ import { Component } from '@angular/core';
|
|||
import { FormBuilder } from '@angular/forms';
|
||||
import { SafeUrl } from '@angular/platform-browser';
|
||||
import { CoreAnyError } from '@classes/errors/error';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreGeolocation, CoreGeolocationError, CoreGeolocationErrorReason } from '@services/geolocation';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { DomSanitizer } from '@singletons';
|
||||
|
||||
|
@ -73,7 +73,7 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginBa
|
|||
const northFixed = north ? north.toFixed(4) : '0.0000';
|
||||
const eastFixed = east ? east.toFixed(4) : '0.0000';
|
||||
|
||||
if (CoreApp.isIOS()) {
|
||||
if (CorePlatform.isIOS()) {
|
||||
url = 'http://maps.apple.com/?ll=' + northFixed + ',' + eastFixed + '&near=' + northFixed + ',' + eastFixed;
|
||||
} else {
|
||||
url = 'geo:' + northFixed + ',' + eastFixed;
|
||||
|
|
|
@ -47,7 +47,7 @@ import { CanLeave } from '@guards/can-leave';
|
|||
import { CoreForms } from '@singletons/form';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
import { CoreTime } from '@singletons/time';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
|
||||
/**
|
||||
* Page that allows attempting a quiz.
|
||||
|
@ -690,7 +690,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
|
|||
*/
|
||||
protected async scrollToQuestion(slot: number): Promise<void> {
|
||||
await CoreUtils.nextTick();
|
||||
await CoreComponentsRegistry.waitComponentsReady(this.elementRef.nativeElement, 'core-question');
|
||||
await CoreDirectivesRegistry.waitDirectivesReady(this.elementRef.nativeElement, 'core-question');
|
||||
await CoreDom.scrollToElement(
|
||||
this.elementRef.nativeElement,
|
||||
'#addon-mod_quiz-question-' + slot,
|
||||
|
|
|
@ -19,7 +19,6 @@ import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/
|
|||
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
|
||||
import { CoreCourse } from '@features/course/services/course';
|
||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreNetwork } from '@services/network';
|
||||
import { CoreFileHelper } from '@services/file-helper';
|
||||
import { CoreSites } from '@services/sites';
|
||||
|
@ -35,6 +34,7 @@ import {
|
|||
AddonModResourceProvider,
|
||||
} from '../../services/resource';
|
||||
import { AddonModResourceHelper } from '../../services/resource-helper';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
|
||||
/**
|
||||
* Component that displays a resource.
|
||||
|
@ -79,7 +79,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
|
|||
async ngOnInit(): Promise<void> {
|
||||
super.ngOnInit();
|
||||
|
||||
this.isIOS = CoreApp.isIOS();
|
||||
this.isIOS = CorePlatform.isIOS();
|
||||
this.isOnline = CoreNetwork.isOnline();
|
||||
|
||||
// Refresh online status when changes.
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
import { CoreFormatTextDirective } from '@directives/format-text';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
import { CoreCoordinates, CoreDom } from '@singletons/dom';
|
||||
import { CoreEventObserver } from '@singletons/events';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
|
@ -427,7 +427,7 @@ export class AddonQtypeDdwtosQuestion {
|
|||
protected async waitForReady(): Promise<void> {
|
||||
await CoreDom.waitToBeInDOM(this.container);
|
||||
|
||||
await CoreComponentsRegistry.waitComponentsReady(this.container, 'core-format-text', CoreFormatTextDirective);
|
||||
await CoreDirectivesRegistry.waitDirectivesReady(this.container, 'core-format-text', CoreFormatTextDirective);
|
||||
|
||||
const drag = Array.from(this.container.querySelectorAll<HTMLElement>(this.selectors.dragHomes()))[0];
|
||||
|
||||
|
|
|
@ -13,12 +13,12 @@
|
|||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* Component that is not rendered immediately after being mounted.
|
||||
* Directive that is not rendered immediately after being mounted.
|
||||
*/
|
||||
export interface AsyncComponent {
|
||||
export interface AsyncDirective {
|
||||
|
||||
/**
|
||||
* Wait until the component is fully rendered and ready.
|
||||
* Wait until the directive is fully rendered and ready.
|
||||
*/
|
||||
ready(): Promise<void>;
|
||||
}
|
|
@ -18,6 +18,7 @@
|
|||
export abstract class ElementController {
|
||||
|
||||
protected enabled: boolean;
|
||||
protected destroyed = false;
|
||||
|
||||
constructor(enabled: boolean) {
|
||||
this.enabled = enabled;
|
||||
|
@ -49,6 +50,19 @@ export abstract class ElementController {
|
|||
this.onDisabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the element.
|
||||
*/
|
||||
destroy(): void {
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.destroyed = true;
|
||||
|
||||
this.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update underlying element to enable interactivity.
|
||||
*/
|
||||
|
@ -59,4 +73,11 @@ export abstract class ElementController {
|
|||
*/
|
||||
abstract onDisabled(): void;
|
||||
|
||||
/**
|
||||
* Destroy/dispose pertinent data.
|
||||
*/
|
||||
onDestroy(): void {
|
||||
// By default, nothing to destroy.
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,6 +14,10 @@
|
|||
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { ElementController } from './ElementController';
|
||||
import videojs, { VideoJSPlayer } from 'video.js';
|
||||
import { CorePromisedValue } from '@classes/promised-value';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { CoreMedia } from '@singletons/media';
|
||||
|
||||
/**
|
||||
* Wrapper class to control the interactivity of a media element.
|
||||
|
@ -25,6 +29,10 @@ export class MediaElementController extends ElementController {
|
|||
private playing?: boolean;
|
||||
private playListener?: () => void;
|
||||
private pauseListener?: () => void;
|
||||
private jsPlayer = new CorePromisedValue<VideoJSPlayer | null>();
|
||||
private jsPlayerListener?: CoreEventObserver;
|
||||
private shouldEnable = false;
|
||||
private shouldDisable = false;
|
||||
|
||||
constructor(media: HTMLMediaElement, enabled: boolean) {
|
||||
super(enabled);
|
||||
|
@ -34,48 +42,119 @@ export class MediaElementController extends ElementController {
|
|||
|
||||
media.autoplay = false;
|
||||
|
||||
if (CoreMedia.mediaUsesJavascriptPlayer(media)) {
|
||||
const player = this.searchJSPlayer();
|
||||
if (player) {
|
||||
this.jsPlayer.resolve(player);
|
||||
} else {
|
||||
this.jsPlayerListener = CoreEvents.on(CoreEvents.JS_PLAYER_CREATED, data => {
|
||||
if (data.element === media) {
|
||||
this.jsPlayerListener?.off();
|
||||
this.jsPlayer.resolve(data.player);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.jsPlayer.resolve(null);
|
||||
}
|
||||
|
||||
enabled && this.onEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
onEnabled(): void {
|
||||
async onEnabled(): Promise<void> {
|
||||
this.shouldEnable = true;
|
||||
this.shouldDisable = false;
|
||||
|
||||
const jsPlayer = await this.jsPlayer;
|
||||
|
||||
if (!this.shouldEnable || this.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ready = this.playing ?? this.autoplay
|
||||
? this.media.play()
|
||||
? (jsPlayer ?? this.media).play()
|
||||
: Promise.resolve();
|
||||
|
||||
ready
|
||||
.then(() => this.addPlaybackEventListeners())
|
||||
.catch(error => CoreUtils.logUnhandledError('Error enabling media element', error));
|
||||
try {
|
||||
await ready;
|
||||
|
||||
this.addPlaybackEventListeners(jsPlayer);
|
||||
} catch (error) {
|
||||
CoreUtils.logUnhandledError('Error enabling media element', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async onDisabled(): Promise<void> {
|
||||
this.removePlaybackEventListeners();
|
||||
this.shouldDisable = true;
|
||||
this.shouldEnable = false;
|
||||
|
||||
this.media.pause();
|
||||
const jsPlayer = await this.jsPlayer;
|
||||
|
||||
if (!this.shouldDisable || this.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.removePlaybackEventListeners(jsPlayer);
|
||||
|
||||
(jsPlayer ?? this.media).pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async onDestroy(): Promise<void> {
|
||||
const jsPlayer = await this.jsPlayer;
|
||||
|
||||
this.removePlaybackEventListeners(jsPlayer);
|
||||
jsPlayer?.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening playback events.
|
||||
*
|
||||
* @param jsPlayer Javascript player instance (if any).
|
||||
*/
|
||||
private addPlaybackEventListeners(): void {
|
||||
private addPlaybackEventListeners(jsPlayer: VideoJSPlayer | null): void {
|
||||
if (jsPlayer) {
|
||||
jsPlayer.on('play', this.playListener = () => this.playing = true);
|
||||
jsPlayer.on('pause', this.pauseListener = () => this.playing = false);
|
||||
} else {
|
||||
this.media.addEventListener('play', this.playListener = () => this.playing = true);
|
||||
this.media.addEventListener('pause', this.pauseListener = () => this.playing = false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop listening playback events.
|
||||
*
|
||||
* @param jsPlayer Javascript player instance (if any).
|
||||
*/
|
||||
private removePlaybackEventListeners(): void {
|
||||
private removePlaybackEventListeners(jsPlayer: VideoJSPlayer | null): void {
|
||||
if (jsPlayer) {
|
||||
this.playListener && jsPlayer.off('play', this.playListener);
|
||||
this.pauseListener && jsPlayer.off('pause', this.pauseListener);
|
||||
} else {
|
||||
this.playListener && this.media.removeEventListener('play', this.playListener);
|
||||
this.pauseListener && this.media.removeEventListener('pause', this.pauseListener);
|
||||
}
|
||||
|
||||
delete this.playListener;
|
||||
delete this.pauseListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search JS player instance.
|
||||
*
|
||||
* @returns Player instance if found.
|
||||
*/
|
||||
private searchJSPlayer(): VideoJSPlayer | null {
|
||||
return videojs.getPlayer(this.media.id) || videojs.getPlayer(this.media.id.replace('_html5_api', ''));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
import { CoreSitesReadingStrategy } from '@services/sites';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { AsyncComponent } from './async-component';
|
||||
import { AsyncDirective } from './async-directive';
|
||||
import { PageLoadsManager } from './page-loads-manager';
|
||||
import { CorePromisedValue } from './promised-value';
|
||||
import { WSObservable } from './site';
|
||||
|
@ -27,7 +27,7 @@ export class PageLoadWatcher {
|
|||
|
||||
protected hasChanges = false;
|
||||
protected ongoingRequests = 0;
|
||||
protected components = new Set<AsyncComponent>();
|
||||
protected components = new Set<AsyncDirective>();
|
||||
protected loadedTimeout?: number;
|
||||
protected hasChangesPromises: Promise<boolean>[] = [];
|
||||
|
||||
|
@ -66,7 +66,7 @@ export class PageLoadWatcher {
|
|||
*
|
||||
* @param component Component instance.
|
||||
*/
|
||||
async watchComponent(component: AsyncComponent): Promise<void> {
|
||||
async watchComponent(component: AsyncDirective): Promise<void> {
|
||||
this.components.add(component);
|
||||
clearTimeout(this.loadedTimeout);
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import { CoreRefreshButtonModalComponent } from '@components/refresh-button-moda
|
|||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { Subject } from 'rxjs';
|
||||
import { AsyncComponent } from './async-component';
|
||||
import { AsyncDirective } from './async-directive';
|
||||
import { PageLoadWatcher } from './page-load-watcher';
|
||||
|
||||
/**
|
||||
|
@ -37,7 +37,7 @@ export class PageLoadsManager {
|
|||
* @param staleWhileRevalidate Whether to use stale while revalidate strategy.
|
||||
* @returns Load watcher to use.
|
||||
*/
|
||||
startPageLoad(page: AsyncComponent, staleWhileRevalidate: boolean): PageLoadWatcher {
|
||||
startPageLoad(page: AsyncDirective, staleWhileRevalidate: boolean): PageLoadWatcher {
|
||||
this.initialPath = this.initialPath ?? CoreNavigator.getCurrentPath();
|
||||
this.currentLoadWatcher = new PageLoadWatcher(this, staleWhileRevalidate);
|
||||
this.ongoingLoadWatchers.add(this.currentLoadWatcher);
|
||||
|
@ -53,7 +53,7 @@ export class PageLoadsManager {
|
|||
* @param component Component instance.
|
||||
* @returns Load watcher to use.
|
||||
*/
|
||||
startComponentLoad(component: AsyncComponent): PageLoadWatcher {
|
||||
startComponentLoad(component: AsyncDirective): PageLoadWatcher {
|
||||
// If a component is loading data without the page loading data, probably the component is reloading/refreshing.
|
||||
// In that case, create a load watcher instance but don't store it in currentLoadWatcher because it's not a page load.
|
||||
const loadWatcher = this.currentLoadWatcher ?? new PageLoadWatcher(this, false);
|
||||
|
|
|
@ -37,8 +37,8 @@ import { CoreDom } from '@singletons/dom';
|
|||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreError } from './errors/error';
|
||||
import { CorePromisedValue } from './promised-value';
|
||||
import { AsyncComponent } from './async-component';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { AsyncDirective } from './async-directive';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
|
||||
/**
|
||||
|
@ -47,7 +47,7 @@ import { CorePlatform } from '@services/platform';
|
|||
@Component({
|
||||
template: '',
|
||||
})
|
||||
export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, AfterViewInit, OnChanges, OnDestroy, AsyncComponent {
|
||||
export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, AfterViewInit, OnChanges, OnDestroy, AsyncDirective {
|
||||
|
||||
// Minimum tab's width.
|
||||
protected static readonly MIN_TAB_WIDTH = 107;
|
||||
|
@ -99,7 +99,7 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
|
|||
|
||||
this.tabAction = new CoreTabsRoleTab(this);
|
||||
|
||||
CoreComponentsRegistry.register(element.nativeElement, this);
|
||||
CoreDirectivesRegistry.register(element.nativeElement, this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,7 +20,7 @@ import { CoreUtils } from '@services/utils/utils';
|
|||
import { Translate } from '@singletons';
|
||||
import { CoreContextMenuItemComponent } from './context-menu-item';
|
||||
import { CoreContextMenuPopoverComponent } from './context-menu-popover';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
|
||||
/**
|
||||
* This component adds a button (usually in the navigation bar) that displays a context menu popover.
|
||||
|
@ -61,7 +61,7 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy {
|
|||
// Calculate the unique ID.
|
||||
this.uniqueId = 'core-context-menu-' + CoreUtils.getUniqueId('CoreContextMenuComponent');
|
||||
|
||||
CoreComponentsRegistry.register(elementRef.nativeElement, this);
|
||||
CoreDirectivesRegistry.register(elementRef.nativeElement, this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Component, Input, Output, OnInit, OnDestroy, EventEmitter } from '@angular/core';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreNetwork } from '@services/network';
|
||||
import { CoreFilepool } from '@services/filepool';
|
||||
import { CoreFileHelper } from '@services/file-helper';
|
||||
|
@ -27,6 +26,7 @@ import { CoreTextUtils } from '@services/utils/text';
|
|||
import { CoreConstants } from '@/core/constants';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { CoreWSFile } from '@services/ws';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
|
||||
/**
|
||||
* Component to handle a remote file. Shows the file name, icon (depending on mimetype) and a button
|
||||
|
@ -87,7 +87,7 @@ export class CoreFileComponent implements OnInit, OnDestroy {
|
|||
this.fileSize = this.file.filesize;
|
||||
this.fileName = this.file.filename || '';
|
||||
|
||||
this.isIOS = CoreApp.isIOS();
|
||||
this.isIOS = CorePlatform.isIOS();
|
||||
this.defaultIsOpenWithPicker = CoreFileHelper.defaultIsOpenWithPicker();
|
||||
this.openButtonIcon = this.defaultIsOpenWithPicker ? 'fas-file' : 'fas-share-square';
|
||||
this.openButtonLabel = this.defaultIsOpenWithPicker ? 'core.openfile' : 'core.openwith';
|
||||
|
|
|
@ -18,10 +18,10 @@ import { CoreEventLoadingChangedData, CoreEvents } from '@singletons/events';
|
|||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreAnimations } from '@components/animations';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
import { CorePromisedValue } from '@classes/promised-value';
|
||||
import { AsyncComponent } from '@classes/async-component';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { AsyncDirective } from '@classes/async-directive';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
|
||||
/**
|
||||
* Component to show a loading spinner and message while data is being loaded.
|
||||
|
@ -49,7 +49,7 @@ import { CoreApp } from '@services/app';
|
|||
styleUrls: ['loading.scss'],
|
||||
animations: [CoreAnimations.SHOW_HIDE],
|
||||
})
|
||||
export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit, AsyncComponent, OnDestroy {
|
||||
export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit, AsyncDirective, OnDestroy {
|
||||
|
||||
@Input() hideUntil: unknown = false; // Determine when should the contents be shown.
|
||||
@Input() message?: string; // Message to show while loading.
|
||||
|
@ -65,7 +65,7 @@ export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit, A
|
|||
|
||||
constructor(element: ElementRef) {
|
||||
this.element = element.nativeElement;
|
||||
CoreComponentsRegistry.register(this.element, this);
|
||||
CoreDirectivesRegistry.register(this.element, this);
|
||||
|
||||
// Calculate the unique ID.
|
||||
this.uniqueId = 'core-loading-content-' + CoreUtils.getUniqueId('CoreLoadingComponent');
|
||||
|
@ -146,7 +146,7 @@ export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit, A
|
|||
if (loaded) {
|
||||
this.onReadyPromise.resolve();
|
||||
this.restoreScrollPosition();
|
||||
if (CoreApp.isIOS()) {
|
||||
if (CorePlatform.isIOS()) {
|
||||
this.mutationObserver.observe(this.element, { childList: true });
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -25,8 +25,8 @@ import { CoreTextUtils } from '@services/utils/text';
|
|||
import { CoreTimeUtils } from '@services/utils/time';
|
||||
import { CoreUtils, CoreUtilsOpenFileOptions, OpenFileAction } from '@services/utils/utils';
|
||||
import { CoreForms } from '@singletons/form';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CorePath } from '@singletons/path';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
|
||||
/**
|
||||
* Component to handle a local file. Only files inside the app folder can be managed.
|
||||
|
@ -83,7 +83,7 @@ export class CoreLocalFileComponent implements OnInit {
|
|||
|
||||
this.timemodified = CoreTimeUtils.userDate(metadata.modificationTime.getTime(), 'core.strftimedatetimeshort');
|
||||
|
||||
this.isIOS = CoreApp.isIOS();
|
||||
this.isIOS = CorePlatform.isIOS();
|
||||
this.defaultIsOpenWithPicker = CoreFileHelper.defaultIsOpenWithPicker();
|
||||
this.openButtonIcon = this.defaultIsOpenWithPicker ? 'fas-file' : 'fas-share-square';
|
||||
this.openButtonLabel = this.defaultIsOpenWithPicker ? 'core.openfile' : 'core.openwith';
|
||||
|
|
|
@ -25,7 +25,7 @@ import {
|
|||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreContextMenuComponent } from '../context-menu/context-menu';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
|
||||
const BUTTON_HIDDEN_CLASS = 'core-navbar-button-hidden';
|
||||
|
@ -82,7 +82,7 @@ export class CoreNavBarButtonsComponent implements OnInit, OnDestroy {
|
|||
this.element = element.nativeElement;
|
||||
this.logger = CoreLogger.getInstance('CoreNavBarButtonsComponent');
|
||||
|
||||
CoreComponentsRegistry.register(this.element, this);
|
||||
CoreDirectivesRegistry.register(this.element, this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -156,11 +156,11 @@ export class CoreNavBarButtonsComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
const mainContextMenu = buttonsContainer.querySelector('core-context-menu');
|
||||
const secondaryContextMenuInstance = CoreComponentsRegistry.resolve(secondaryContextMenu, CoreContextMenuComponent);
|
||||
const secondaryContextMenuInstance = CoreDirectivesRegistry.resolve(secondaryContextMenu, CoreContextMenuComponent);
|
||||
let mainContextMenuInstance: CoreContextMenuComponent | null;
|
||||
if (mainContextMenu) {
|
||||
// Both containers have a context menu. Merge them to prevent having 2 menus at the same time.
|
||||
mainContextMenuInstance = CoreComponentsRegistry.resolve(mainContextMenu, CoreContextMenuComponent);
|
||||
mainContextMenuInstance = CoreDirectivesRegistry.resolve(mainContextMenu, CoreContextMenuComponent);
|
||||
} else {
|
||||
// There is a context-menu in these buttons, but there is no main context menu in the header.
|
||||
// Create one main context menu dynamically.
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
import { Component, OnInit, AfterViewInit, Input, ElementRef, ContentChild } from '@angular/core';
|
||||
import { IonInput } from '@ionic/angular';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
|
||||
|
@ -121,7 +121,7 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit {
|
|||
|
||||
this.setData(this.input);
|
||||
// In Android, the keyboard is closed when the input type changes. Focus it again.
|
||||
if (isFocused && CoreApp.isAndroid()) {
|
||||
if (isFocused && CorePlatform.isAndroid()) {
|
||||
CoreDomUtils.focusElement(this.input);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ import { CoreNavBarButtonsComponent } from '../navbar-buttons/navbar-buttons';
|
|||
import { StackEvent } from '@ionic/angular/directives/navigation/stack-utils';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreTabBase, CoreTabsBaseComponent } from '@classes/tabs';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
|
||||
/**
|
||||
* This component displays some top scrollable tabs that will autohide on vertical scroll.
|
||||
|
@ -207,7 +207,7 @@ export class CoreTabsOutletComponent extends CoreTabsBaseComponent<CoreTabsOutle
|
|||
protected showHideNavBarButtons(activatedPageName: string): void {
|
||||
const elements = this.ionTabs.outlet.nativeEl.querySelectorAll('core-navbar-buttons');
|
||||
elements.forEach((element) => {
|
||||
const instance = CoreComponentsRegistry.resolve(element, CoreNavBarButtonsComponent);
|
||||
const instance = CoreDirectivesRegistry.resolve(element, CoreNavBarButtonsComponent);
|
||||
|
||||
if (instance) {
|
||||
const pagetagName = element.closest('.ion-page')?.tagName;
|
||||
|
|
|
@ -16,7 +16,7 @@ import { Component, Input, Output, OnInit, OnDestroy, ElementRef, EventEmitter,
|
|||
import { CoreTabBase } from '@classes/tabs';
|
||||
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
import { CoreNavBarButtonsComponent } from '../navbar-buttons/navbar-buttons';
|
||||
import { CoreTabsComponent } from './tabs';
|
||||
|
||||
|
@ -140,7 +140,7 @@ export class CoreTabComponent implements OnInit, OnDestroy, CoreTabBase {
|
|||
protected showHideNavBarButtons(show: boolean): void {
|
||||
const elements = this.element.querySelectorAll('core-navbar-buttons');
|
||||
elements.forEach((element) => {
|
||||
const instance = CoreComponentsRegistry.resolve(element, CoreNavBarButtonsComponent);
|
||||
const instance = CoreDirectivesRegistry.resolve(element, CoreNavBarButtonsComponent);
|
||||
|
||||
if (instance) {
|
||||
instance.forceHide(!show);
|
||||
|
|
|
@ -17,7 +17,7 @@ import { ScrollDetail } from '@ionic/core';
|
|||
import { IonContent } from '@ionic/angular';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreMath } from '@singletons/math';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
import { CoreFormatTextDirective } from './format-text';
|
||||
import { CoreEventObserver } from '@singletons/events';
|
||||
import { CoreLoadingComponent } from '@components/loading/loading';
|
||||
|
@ -203,7 +203,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
|
|||
* Wait until all <core-format-text> children inside the element are done rendering.
|
||||
*/
|
||||
protected async waitFormatTextsRendered(): Promise<void> {
|
||||
await CoreComponentsRegistry.waitComponentsReady(this.element, 'core-format-text', CoreFormatTextDirective);
|
||||
await CoreDirectivesRegistry.waitDirectivesReady(this.element, 'core-format-text', CoreFormatTextDirective);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -249,8 +249,8 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
|
|||
const scrollElement = await this.ionContent.getScrollElement();
|
||||
|
||||
await Promise.all([
|
||||
await CoreComponentsRegistry.waitComponentsReady(scrollElement, 'core-loading', CoreLoadingComponent),
|
||||
await CoreComponentsRegistry.waitComponentsReady(this.element, 'core-loading', CoreLoadingComponent),
|
||||
await CoreDirectivesRegistry.waitDirectivesReady(scrollElement, 'core-loading', CoreLoadingComponent),
|
||||
await CoreDirectivesRegistry.waitDirectivesReady(this.element, 'core-loading', CoreLoadingComponent),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ import { CoreTabsComponent } from '@components/tabs/tabs';
|
|||
import { CoreSettingsHelper } from '@features/settings/services/settings-helper';
|
||||
import { ScrollDetail } from '@ionic/core';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { CoreMath } from '@singletons/math';
|
||||
|
@ -294,7 +294,7 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
|
|||
this.listenEvents();
|
||||
|
||||
// Initialize from tabs.
|
||||
const tabs = CoreComponentsRegistry.resolve(this.page.querySelector('core-tabs-outlet'), CoreTabsOutletComponent);
|
||||
const tabs = CoreDirectivesRegistry.resolve(this.page.querySelector('core-tabs-outlet'), CoreTabsOutletComponent);
|
||||
|
||||
if (tabs) {
|
||||
const outlet = tabs.getOutlet();
|
||||
|
@ -424,14 +424,14 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
|
|||
}
|
||||
|
||||
// Wait loadings to finish.
|
||||
await CoreComponentsRegistry.waitComponentsReady(this.page, 'core-loading', CoreLoadingComponent);
|
||||
await CoreDirectivesRegistry.waitDirectivesReady(this.page, 'core-loading', CoreLoadingComponent);
|
||||
|
||||
// Wait tabs to be ready.
|
||||
await CoreComponentsRegistry.waitComponentsReady(this.page, 'core-tabs', CoreTabsComponent);
|
||||
await CoreComponentsRegistry.waitComponentsReady(this.page, 'core-tabs-outlet', CoreTabsOutletComponent);
|
||||
await CoreDirectivesRegistry.waitDirectivesReady(this.page, 'core-tabs', CoreTabsComponent);
|
||||
await CoreDirectivesRegistry.waitDirectivesReady(this.page, 'core-tabs-outlet', CoreTabsOutletComponent);
|
||||
|
||||
// Wait loadings to finish, inside tabs (if any).
|
||||
await CoreComponentsRegistry.waitComponentsReady(
|
||||
await CoreDirectivesRegistry.waitDirectivesReady(
|
||||
this.page,
|
||||
'core-tab core-loading, ion-router-outlet core-loading',
|
||||
CoreLoadingComponent,
|
||||
|
@ -445,7 +445,7 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
|
|||
* @returns Promise resolved when texts are rendered.
|
||||
*/
|
||||
protected async waitFormatTextsRendered(element: Element): Promise<void> {
|
||||
await CoreComponentsRegistry.waitComponentsReady(element, 'core-format-text', CoreFormatTextDirective);
|
||||
await CoreDirectivesRegistry.waitDirectivesReady(element, 'core-format-text', CoreFormatTextDirective);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,7 +19,7 @@ import { CoreSettingsHelper } from '@features/settings/services/settings-helper'
|
|||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreColors } from '@singletons/colors';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
import { CoreEventObserver } from '@singletons/events';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
@ -128,14 +128,14 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
|
|||
return;
|
||||
}
|
||||
|
||||
await CoreComponentsRegistry.waitComponentsReady(this.page, 'core-loading', CoreLoadingComponent);
|
||||
await CoreDirectivesRegistry.waitDirectivesReady(this.page, 'core-loading', CoreLoadingComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until all <core-format-text> children inside the element are done rendering.
|
||||
*/
|
||||
protected async waitFormatTextsRendered(): Promise<void> {
|
||||
await CoreComponentsRegistry.waitComponentsReady(this.element, 'core-format-text', CoreFormatTextDirective);
|
||||
await CoreDirectivesRegistry.waitDirectivesReady(this.element, 'core-format-text', CoreFormatTextDirective);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -23,7 +23,6 @@ import {
|
|||
EventEmitter,
|
||||
OnDestroy,
|
||||
} from '@angular/core';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreFile } from '@services/file';
|
||||
import { CoreFilepool, CoreFilepoolFileActions, CoreFilepoolFileEventData } from '@services/filepool';
|
||||
import { CoreSites } from '@services/sites';
|
||||
|
@ -36,6 +35,10 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
|||
import { CoreConstants } from '../constants';
|
||||
import { CoreNetwork } from '@services/network';
|
||||
import { Translate } from '@singletons';
|
||||
import { AsyncDirective } from '@classes/async-directive';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
import { CorePromisedValue } from '@classes/promised-value';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
|
||||
/**
|
||||
* Directive to handle external content.
|
||||
|
@ -50,7 +53,7 @@ import { Translate } from '@singletons';
|
|||
@Directive({
|
||||
selector: '[core-external-content]',
|
||||
})
|
||||
export class CoreExternalContentDirective implements AfterViewInit, OnChanges, OnDestroy {
|
||||
export class CoreExternalContentDirective implements AfterViewInit, OnChanges, OnDestroy, AsyncDirective {
|
||||
|
||||
@Input() siteId?: string; // Site ID to use.
|
||||
@Input() component?: string; // Component to link the file to.
|
||||
|
@ -67,11 +70,14 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O
|
|||
protected logger: CoreLogger;
|
||||
protected initialized = false;
|
||||
protected fileEventObserver?: CoreEventObserver;
|
||||
protected onReadyPromise = new CorePromisedValue<void>();
|
||||
|
||||
constructor(element: ElementRef) {
|
||||
|
||||
this.element = element.nativeElement;
|
||||
this.logger = CoreLogger.getInstance('CoreExternalContentDirective');
|
||||
|
||||
CoreDirectivesRegistry.register(this.element, this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -111,7 +117,7 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O
|
|||
newSource.setAttribute('src', url);
|
||||
|
||||
if (type) {
|
||||
if (CoreApp.isAndroid() && type == 'video/quicktime') {
|
||||
if (CorePlatform.isAndroid() && type == 'video/quicktime') {
|
||||
// Fix for VideoJS/Chrome bug https://github.com/videojs/video.js/issues/423 .
|
||||
newSource.setAttribute('type', 'video/mp4');
|
||||
} else {
|
||||
|
@ -157,15 +163,21 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O
|
|||
|
||||
} else {
|
||||
this.invalid = true;
|
||||
this.onReadyPromise.resolve();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid handling data url's.
|
||||
if (url && url.indexOf('data:') === 0) {
|
||||
this.invalid = true;
|
||||
if (tagName === 'SOURCE') {
|
||||
// Restoring original src.
|
||||
this.addSource(url);
|
||||
}
|
||||
|
||||
this.onLoad.emit();
|
||||
this.loaded = true;
|
||||
this.onReadyPromise.resolve();
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -182,6 +194,8 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O
|
|||
this.loaded = true;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.onReadyPromise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,13 +280,11 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O
|
|||
return;
|
||||
}
|
||||
|
||||
let urls = inlineStyles.match(/https?:\/\/[^"') ;]*/g);
|
||||
if (!urls || !urls.length) {
|
||||
const urls = CoreUtils.uniqueArray(Array.from(inlineStyles.match(/https?:\/\/[^"') ;]*/g) ?? []));
|
||||
if (!urls.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
urls = CoreUtils.uniqueArray(urls); // Remove duplicates.
|
||||
|
||||
const promises = urls.map(async (url) => {
|
||||
const finalUrl = await CoreFilepool.getSrcByUrl(siteId, url, this.component, this.componentId, 0, true, true);
|
||||
|
||||
|
@ -462,4 +474,11 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O
|
|||
this.fileEventObserver?.off();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ready(): Promise<void> {
|
||||
return this.onReadyPromise;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -42,10 +42,10 @@ import { CoreFilter, CoreFilterFilter, CoreFilterFormatTextOptions } from '@feat
|
|||
import { CoreFilterDelegate } from '@features/filter/services/filter-delegate';
|
||||
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
|
||||
import { CoreSubscriptions } from '@singletons/subscriptions';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
import { CoreCollapsibleItemDirective } from './collapsible-item';
|
||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||
import { AsyncComponent } from '@classes/async-component';
|
||||
import { AsyncDirective } from '@classes/async-directive';
|
||||
import { CorePath } from '@singletons/path';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
|
@ -67,7 +67,7 @@ import { FrameElementController } from '@classes/element-controllers/FrameElemen
|
|||
@Directive({
|
||||
selector: 'core-format-text',
|
||||
})
|
||||
export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncComponent {
|
||||
export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirective {
|
||||
|
||||
@ViewChild(CoreCollapsibleItemDirective) collapsible?: CoreCollapsibleItemDirective;
|
||||
|
||||
|
@ -111,7 +111,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
|||
protected viewContainerRef: ViewContainerRef,
|
||||
@Optional() @Inject(CORE_REFRESH_CONTEXT) protected refreshContext?: CoreRefreshContext,
|
||||
) {
|
||||
CoreComponentsRegistry.register(element.nativeElement, this);
|
||||
CoreDirectivesRegistry.register(element.nativeElement, this);
|
||||
|
||||
this.element = element.nativeElement;
|
||||
this.element.classList.add('core-loading'); // Hide contents until they're treated.
|
||||
|
@ -149,6 +149,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
|||
ngOnDestroy(): void {
|
||||
this.domElementPromise?.cancel();
|
||||
this.domPromises.forEach((promise) => { promise.cancel();});
|
||||
this.elementControllers.forEach(controller => controller.destroy());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -365,6 +366,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
|||
// Move the children to the current element to be able to calculate the height.
|
||||
CoreDomUtils.moveChildren(result.div, this.element);
|
||||
|
||||
this.elementControllers.forEach(controller => controller.destroy());
|
||||
this.elementControllers = result.elementControllers;
|
||||
|
||||
await CoreUtils.nextTick();
|
||||
|
|
|
@ -21,7 +21,7 @@ import { CoreCourseBlock } from '../../course/services/course';
|
|||
import { Params } from '@angular/router';
|
||||
import { ContextLevel } from '@/core/constants';
|
||||
import { CoreNavigationOptions } from '@services/navigator';
|
||||
import { AsyncComponent } from '@classes/async-component';
|
||||
import { AsyncDirective } from '@classes/async-directive';
|
||||
import { CorePromisedValue } from '@classes/promised-value';
|
||||
|
||||
/**
|
||||
|
@ -30,7 +30,7 @@ import { CorePromisedValue } from '@classes/promised-value';
|
|||
@Component({
|
||||
template: '',
|
||||
})
|
||||
export abstract class CoreBlockBaseComponent implements OnInit, ICoreBlockComponent, AsyncComponent {
|
||||
export abstract class CoreBlockBaseComponent implements OnInit, ICoreBlockComponent, AsyncDirective {
|
||||
|
||||
@Input() title!: string; // The block title.
|
||||
@Input() block!: CoreCourseBlock; // The block to render.
|
||||
|
|
|
@ -79,6 +79,7 @@ import { Md5 } from 'ts-md5/dist/md5';
|
|||
import { CoreSyncBaseProvider } from '@classes/base-sync';
|
||||
import { CoreArray } from '@singletons/array';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
import { CoreForms } from '@singletons/form';
|
||||
import { CoreText } from '@singletons/text';
|
||||
|
@ -350,6 +351,7 @@ export class CoreCompileProvider {
|
|||
instance['CoreSyncBaseProvider'] = CoreSyncBaseProvider;
|
||||
instance['CoreArray'] = CoreArray;
|
||||
instance['CoreComponentsRegistry'] = CoreComponentsRegistry;
|
||||
instance['CoreDirectivesRegistry'] = CoreDirectivesRegistry;
|
||||
instance['CoreNetwork'] = CoreNetwork.instance;
|
||||
instance['CorePlatform'] = CorePlatform.instance;
|
||||
instance['CoreDom'] = CoreDom;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
import { AddonBlockMyOverviewComponent } from '@addons/block/myoverview/components/myoverview/myoverview';
|
||||
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { AsyncComponent } from '@classes/async-component';
|
||||
import { AsyncDirective } from '@classes/async-directive';
|
||||
import { PageLoadsManager } from '@classes/page-loads-manager';
|
||||
import { CorePromisedValue } from '@classes/promised-value';
|
||||
import { CoreBlockComponent } from '@features/block/components/block/block';
|
||||
|
@ -42,7 +42,7 @@ import { CoreCourses } from '../../services/courses';
|
|||
useClass: PageLoadsManager,
|
||||
}],
|
||||
})
|
||||
export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy, AsyncComponent {
|
||||
export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy, AsyncDirective {
|
||||
|
||||
@ViewChild(CoreBlockComponent) block!: CoreBlockComponent;
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ import { CoreUtils } from '@services/utils/utils';
|
|||
import { Translate } from '@singletons';
|
||||
import { CoreEventFormActionData, CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { CoreEditorOffline } from '../../services/editor-offline';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
import { CoreLoadingComponent } from '@components/loading/loading';
|
||||
import { CoreScreen } from '@services/screen';
|
||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||
|
@ -304,7 +304,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
|
|||
return;
|
||||
}
|
||||
|
||||
await CoreComponentsRegistry.waitComponentsReady(page, 'core-loading', CoreLoadingComponent);
|
||||
await CoreDirectivesRegistry.waitDirectivesReady(page, 'core-loading', CoreLoadingComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,7 +19,6 @@ import { ChooserResult } from '@ionic-native/chooser/ngx';
|
|||
import { FileEntry, IFile } from '@ionic-native/file/ngx';
|
||||
import { MediaFile } from '@ionic-native/media-capture/ngx';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreNetwork } from '@services/network';
|
||||
import { CoreFile, CoreFileProvider, CoreFileProgressEvent } from '@services/file';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
|
@ -652,7 +651,7 @@ export class CoreFileUploaderHelperProvider {
|
|||
options.mediaType = Camera.MediaType.PICTURE;
|
||||
} else if (!imageSupported && videoSupported) {
|
||||
options.mediaType = Camera.MediaType.VIDEO;
|
||||
} else if (CoreApp.isIOS()) {
|
||||
} else if (CorePlatform.isIOS()) {
|
||||
// Only get all media in iOS because in Android using this option allows uploading any kind of file.
|
||||
options.mediaType = Camera.MediaType.ALLMEDIA;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ import { FileEntry } from '@ionic-native/file/ngx';
|
|||
import { MediaFile, CaptureError, CaptureAudioOptions, CaptureVideoOptions } from '@ionic-native/media-capture/ngx';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreFile, CoreFileProvider } from '@services/file';
|
||||
import { CoreFilepool } from '@services/filepool';
|
||||
import { CoreSites } from '@services/sites';
|
||||
|
@ -33,6 +32,7 @@ import { CoreError } from '@classes/errors/error';
|
|||
import { CoreSite } from '@classes/site';
|
||||
import { CoreFileEntry, CoreFileHelper } from '@services/file-helper';
|
||||
import { CorePath } from '@singletons/path';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
|
||||
/**
|
||||
* File upload options.
|
||||
|
@ -236,7 +236,7 @@ export class CoreFileUploaderProvider {
|
|||
getCameraUploadOptions(uri: string, isFromAlbum?: boolean): CoreFileUploaderOptions {
|
||||
const extension = CoreMimetypeUtils.guessExtensionFromUrl(uri);
|
||||
const mimetype = CoreMimetypeUtils.getMimeType(extension);
|
||||
const isIOS = CoreApp.isIOS();
|
||||
const isIOS = CorePlatform.isIOS();
|
||||
const options: CoreFileUploaderOptions = {
|
||||
deleteAfterUpload: !isFromAlbum,
|
||||
mimeType: mimetype,
|
||||
|
@ -259,7 +259,7 @@ export class CoreFileUploaderProvider {
|
|||
// If the file was picked from the album, delete it only if it was copied to the app's folder.
|
||||
options.deleteAfterUpload = CoreFile.isFileInAppFolder(uri);
|
||||
|
||||
if (CoreApp.isAndroid()) {
|
||||
if (CorePlatform.isAndroid()) {
|
||||
// Picking an image from album in Android adds a timestamp at the end of the file. Delete it.
|
||||
options.fileName = options.fileName.replace(/(\.[^.]*)\?[^.]*$/, '$1');
|
||||
}
|
||||
|
|
|
@ -45,10 +45,10 @@ export class CoreFileUploaderAudioHandlerService implements CoreFileUploaderHand
|
|||
* @returns Supported mimetypes.
|
||||
*/
|
||||
getSupportedMimetypes(mimetypes: string[]): string[] {
|
||||
if (CoreApp.isIOS()) {
|
||||
if (CorePlatform.isIOS()) {
|
||||
// In iOS it's recorded as WAV.
|
||||
return CoreUtils.filterByRegexp(mimetypes, /^audio\/wav$/);
|
||||
} else if (CoreApp.isAndroid()) {
|
||||
} else if (CorePlatform.isAndroid()) {
|
||||
// In Android we don't know the format the audio will be recorded, so accept any audio mimetype.
|
||||
return CoreUtils.filterByRegexp(mimetypes, /^audio\//);
|
||||
} else {
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreFileUploaderHandler, CoreFileUploaderHandlerData, CoreFileUploaderHandlerResult } from '../fileuploader-delegate';
|
||||
import { CoreFileUploaderHelper } from '../fileuploader-helper';
|
||||
|
@ -94,7 +93,7 @@ export class CoreFileUploaderFileHandlerService implements CoreFileUploaderHandl
|
|||
const input = document.createElement('input');
|
||||
input.setAttribute('type', 'file');
|
||||
input.classList.add('core-fileuploader-file-handler-input');
|
||||
if (mimetypes && mimetypes.length && (!CoreApp.isAndroid() || mimetypes.length == 1)) {
|
||||
if (mimetypes && mimetypes.length && (!CorePlatform.isAndroid() || mimetypes.length == 1)) {
|
||||
// Don't use accept attribute in Android with several mimetypes, it's not supported.
|
||||
input.setAttribute('accept', mimetypes.join(', '));
|
||||
}
|
||||
|
@ -134,7 +133,7 @@ export class CoreFileUploaderFileHandlerService implements CoreFileUploaderHandl
|
|||
}
|
||||
});
|
||||
|
||||
if (CoreApp.isIOS()) {
|
||||
if (CorePlatform.isIOS()) {
|
||||
// In iOS, the click on the input stopped working for some reason. We need to put it 1 level higher.
|
||||
element.parentElement?.appendChild(input);
|
||||
|
||||
|
|
|
@ -45,10 +45,10 @@ export class CoreFileUploaderVideoHandlerService implements CoreFileUploaderHand
|
|||
* @returns Supported mimetypes.
|
||||
*/
|
||||
getSupportedMimetypes(mimetypes: string[]): string[] {
|
||||
if (CoreApp.isIOS()) {
|
||||
if (CorePlatform.isIOS()) {
|
||||
// In iOS it's recorded as MOV.
|
||||
return CoreUtils.filterByRegexp(mimetypes, /^video\/quicktime$/);
|
||||
} else if (CoreApp.isAndroid()) {
|
||||
} else if (CorePlatform.isAndroid()) {
|
||||
// In Android we don't know the format the video will be recorded, so accept any video mimetype.
|
||||
return CoreUtils.filterByRegexp(mimetypes, /^video\//);
|
||||
} else {
|
||||
|
|
|
@ -32,6 +32,7 @@ import { CoreUserSupport } from '@features/user/services/support';
|
|||
import { CoreUserSupportConfig } from '@features/user/classes/support/support-config';
|
||||
import { CoreUserGuestSupportConfig } from '@features/user/classes/support/guest-support-config';
|
||||
import { SafeHtml } from '@angular/platform-browser';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
|
||||
/**
|
||||
* Page to enter the user credentials.
|
||||
|
@ -108,7 +109,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
|
|||
this.pageLoaded = true;
|
||||
}
|
||||
|
||||
if (CoreApp.isIOS()) {
|
||||
if (CorePlatform.isIOS()) {
|
||||
// Make iOS auto-fill work. The field that isn't focused doesn't get updated, do it manually.
|
||||
// Debounce it to prevent triggering this function too often when the user is typing.
|
||||
this.valueChangeSubscription = this.credForm.valueChanges.pipe(debounceTime(1000)).subscribe((changes) => {
|
||||
|
|
|
@ -45,6 +45,7 @@ import { CoreErrorInfoComponent } from '@components/error-info/error-info';
|
|||
import { CoreUserSupportConfig } from '@features/user/classes/support/support-config';
|
||||
import { CoreUserGuestSupportConfig } from '@features/user/classes/support/guest-support-config';
|
||||
import { CoreLoginError } from '@classes/errors/loginerror';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
|
||||
/**
|
||||
* Site (url) chooser when adding a new site.
|
||||
|
@ -93,7 +94,7 @@ export class CoreLoginSitePage implements OnInit {
|
|||
// Load fixed sites if they're set.
|
||||
if (CoreLoginHelper.hasSeveralFixedSites()) {
|
||||
url = this.initSiteSelector();
|
||||
} else if (CoreConstants.CONFIG.enableonboarding && !CoreApp.isIOS()) {
|
||||
} else if (CoreConstants.CONFIG.enableonboarding && !CorePlatform.isIOS()) {
|
||||
this.initOnboarding();
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ import { IonTabs } from '@ionic/angular';
|
|||
import { BackButtonEvent } from '@ionic/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreEvents, CoreEventObserver } from '@singletons/events';
|
||||
import { CoreMainMenu, CoreMainMenuProvider } from '../../services/mainmenu';
|
||||
import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../services/mainmenu-delegate';
|
||||
|
@ -31,6 +30,7 @@ import { trigger, state, style, transition, animate } from '@angular/animations'
|
|||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
|
||||
const ANIMATION_DURATION = 500;
|
||||
|
||||
|
@ -135,7 +135,7 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
|
|||
});
|
||||
document.addEventListener('ionBackButton', this.backButtonFunction);
|
||||
|
||||
if (CoreApp.isIOS()) {
|
||||
if (CorePlatform.isIOS()) {
|
||||
// In iOS, the resize event is triggered before the keyboard is opened/closed and not triggered again once done.
|
||||
// Init handlers again once keyboard is closed since the resize event doesn't have the updated height.
|
||||
this.keyboardObserver = CoreEvents.on(CoreEvents.KEYBOARD_CHANGE, (kbHeight: number) => {
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreLang, CoreLangLanguage } from '@services/lang';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreConstants } from '@/core/constants';
|
||||
|
@ -23,6 +22,7 @@ import { Device, makeSingleton } from '@singletons';
|
|||
import { CoreArray } from '@singletons/array';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreScreen } from '@services/screen';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
|
||||
declare module '@singletons/events' {
|
||||
|
||||
|
@ -196,9 +196,9 @@ export class CoreMainMenuProvider {
|
|||
osversion: Device.version,
|
||||
};
|
||||
|
||||
if (CoreApp.isAndroid()) {
|
||||
if (CorePlatform.isAndroid()) {
|
||||
replacements.devicetype = 'Android';
|
||||
} else if (CoreApp.isIOS()) {
|
||||
} else if (CorePlatform.isIOS()) {
|
||||
replacements.devicetype = 'iPhone or iPad';
|
||||
} else {
|
||||
replacements.devicetype = 'Other';
|
||||
|
|
|
@ -228,7 +228,7 @@ export class CorePushNotificationsProvider {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async createDefaultChannel(): Promise<void> {
|
||||
if (!CoreApp.isAndroid()) {
|
||||
if (!CorePlatform.isAndroid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -481,7 +481,7 @@ export class CorePushNotificationsProvider {
|
|||
text: notification.message,
|
||||
channel: 'PushPluginChannel',
|
||||
};
|
||||
const isAndroid = CoreApp.isAndroid();
|
||||
const isAndroid = CorePlatform.isAndroid();
|
||||
const extraFeatures = CoreUtils.isTrueOrOne(data.extrafeatures);
|
||||
|
||||
if (extraFeatures && isAndroid && CoreUtils.isFalseOrZero(data.notif)) {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Component, Input, Output, OnInit, EventEmitter, ChangeDetectorRef, Type, ElementRef } from '@angular/core';
|
||||
import { AsyncComponent } from '@classes/async-component';
|
||||
import { AsyncDirective } from '@classes/async-directive';
|
||||
import { CorePromisedValue } from '@classes/promised-value';
|
||||
import { CoreQuestionBehaviourDelegate } from '@features/question/services/behaviour-delegate';
|
||||
import { CoreQuestionDelegate } from '@features/question/services/question-delegate';
|
||||
|
@ -22,7 +22,7 @@ import { CoreQuestionBehaviourButton, CoreQuestionHelper, CoreQuestionQuestion }
|
|||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
|
||||
/**
|
||||
|
@ -33,7 +33,7 @@ import { CoreLogger } from '@singletons/logger';
|
|||
templateUrl: 'core-question.html',
|
||||
styleUrls: ['../../question.scss'],
|
||||
})
|
||||
export class CoreQuestionComponent implements OnInit, AsyncComponent {
|
||||
export class CoreQuestionComponent implements OnInit, AsyncDirective {
|
||||
|
||||
@Input() question?: CoreQuestionQuestion; // The question to render.
|
||||
@Input() component?: string; // The component the question belongs to.
|
||||
|
@ -66,7 +66,7 @@ export class CoreQuestionComponent implements OnInit, AsyncComponent {
|
|||
constructor(protected changeDetector: ChangeDetectorRef, private element: ElementRef) {
|
||||
this.logger = CoreLogger.getInstance('CoreQuestionComponent');
|
||||
this.promisedReady = new CorePromisedValue();
|
||||
CoreComponentsRegistry.register(this.element.nativeElement, this);
|
||||
CoreDirectivesRegistry.register(this.element.nativeElement, this);
|
||||
}
|
||||
|
||||
async ready(): Promise<void> {
|
||||
|
|
|
@ -16,7 +16,7 @@ import { CoreConstants } from '@/core/constants';
|
|||
import { Params } from '@angular/router';
|
||||
import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source';
|
||||
import { SHAREDFILES_PAGE_NAME } from '@features/sharedfiles/sharedfiles.module';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
|
||||
/**
|
||||
* Provides a collection of setting sections.
|
||||
|
@ -45,7 +45,7 @@ export class CoreSettingsSectionsSource extends CoreRoutedItemsManagerSource<Cor
|
|||
},
|
||||
];
|
||||
|
||||
if (CoreApp.isIOS()) {
|
||||
if (CorePlatform.isIOS()) {
|
||||
sections.push({
|
||||
name: 'core.sharedfiles.sharedfiles',
|
||||
path: SHAREDFILES_PAGE_NAME + '/list/root',
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { CoreConstants } from '@/core/constants';
|
||||
import { CoreLocalNotifications } from '@services/local-notifications';
|
||||
|
@ -111,10 +110,10 @@ export class CoreSettingsDeviceInfoPage implements OnDestroy {
|
|||
|
||||
if (CorePlatform.isMobile()) {
|
||||
this.deviceInfo.deviceType = CorePlatform.is('tablet') ? 'tablet' : 'phone';
|
||||
if (CoreApp.isAndroid()) {
|
||||
if (CorePlatform.isAndroid()) {
|
||||
this.deviceInfo.deviceOs = 'android';
|
||||
this.deviceOsTranslated = 'Android';
|
||||
} else if (CoreApp.isIOS()) {
|
||||
} else if (CorePlatform.isIOS()) {
|
||||
this.deviceInfo.deviceOs = 'ios';
|
||||
this.deviceOsTranslated = 'iOS';
|
||||
} else {
|
||||
|
|
|
@ -20,13 +20,13 @@ import { CoreLang } from '@services/lang';
|
|||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
|
||||
import { CoreSettingsHelper, CoreColorScheme, CoreZoomLevel } from '../../services/settings-helper';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreIframeUtils } from '@services/utils/iframe';
|
||||
import { Diagnostic, Translate } from '@singletons';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { AlertButton } from '@ionic/angular';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
|
||||
/**
|
||||
* Page that displays the general settings.
|
||||
|
@ -81,7 +81,7 @@ export class CoreSettingsGeneralPage {
|
|||
this.colorSchemes.push(CoreColorScheme.LIGHT);
|
||||
this.selectedScheme = this.colorSchemes[0];
|
||||
} else {
|
||||
this.isAndroid = CoreApp.isAndroid();
|
||||
this.isAndroid = CorePlatform.isAndroid();
|
||||
this.colorSchemes = CoreSettingsHelper.getAllowedColorSchemes();
|
||||
this.selectedScheme = await CoreConfig.get(CoreConstants.SETTINGS_COLOR_SCHEME, CoreColorScheme.LIGHT);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { CoreSettingsHandler, CoreSettingsHandlerData } from '@features/settings/services/settings-delegate';
|
||||
import { SHAREDFILES_PAGE_NAME } from '@features/sharedfiles/sharedfiles.module';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
import { makeSingleton } from '@singletons';
|
||||
|
||||
/**
|
||||
|
@ -33,7 +33,7 @@ export class CoreSharedFilesSettingsHandlerService implements CoreSettingsHandle
|
|||
* @returns Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return CoreApp.isIOS();
|
||||
return CorePlatform.isIOS();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,7 +19,7 @@ import {
|
|||
CoreFileUploaderHandlerData,
|
||||
CoreFileUploaderHandlerResult,
|
||||
} from '@features/fileuploader/services/fileuploader-delegate';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreSharedFilesHelper } from '../sharedfiles-helper';
|
||||
/**
|
||||
|
@ -37,7 +37,7 @@ export class CoreSharedFilesUploadHandlerService implements CoreFileUploaderHand
|
|||
* @returns True or promise resolved with true if enabled.
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return CoreApp.isIOS();
|
||||
return CorePlatform.isIOS();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,7 +18,6 @@ import { FileEntry } from '@ionic-native/file/ngx';
|
|||
import { CoreCanceledError } from '@classes/errors/cancelederror';
|
||||
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
|
||||
import { CoreFileUploaderHandlerResult } from '@features/fileuploader/services/fileuploader-delegate';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreFile } from '@services/file';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreSites } from '@services/sites';
|
||||
|
@ -49,7 +48,7 @@ export class CoreSharedFilesHelperProvider {
|
|||
* Initialize.
|
||||
*/
|
||||
initialize(): void {
|
||||
if (!CoreApp.isIOS()) {
|
||||
if (!CorePlatform.isIOS()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ export class CoreSitePluginsProvider {
|
|||
};
|
||||
|
||||
if (args.appismobile) {
|
||||
defaultArgs.appplatform = CoreApp.isIOS() ? 'ios' : 'android';
|
||||
defaultArgs.appplatform = CorePlatform.isIOS() ? 'ios' : 'android';
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -30,7 +30,7 @@ import { CoreUserToursPopoverLayout } from '@features/usertours/classes/popover-
|
|||
import { CoreUserTours, CoreUserToursAlignment, CoreUserToursSide } from '@features/usertours/services/user-tours';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { AngularFrameworkDelegate } from '@singletons';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { CoreMainMenuProvider } from '@features/mainmenu/services/mainmenu';
|
||||
|
@ -84,7 +84,7 @@ export class CoreUserToursUserTourComponent implements AfterViewInit, OnDestroy
|
|||
constructor({ nativeElement: element }: ElementRef<HTMLElement>) {
|
||||
this.element = element;
|
||||
|
||||
CoreComponentsRegistry.register(element, this);
|
||||
CoreDirectivesRegistry.register(element, this);
|
||||
|
||||
this.element.addEventListener('click', (event) =>
|
||||
this.dismissOnBackOrBackdrop(event.target as HTMLElement));
|
||||
|
|
|
@ -21,7 +21,7 @@ import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/da
|
|||
import { CoreApp } from '@services/app';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { AngularFrameworkDelegate, makeSingleton } from '@singletons';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
import { CoreSubscriptions } from '@singletons/subscriptions';
|
||||
import { CoreUserToursUserTourComponent } from '../components/user-tour/user-tour';
|
||||
|
@ -120,7 +120,7 @@ export class CoreUserToursService {
|
|||
CoreUserToursUserTourComponent,
|
||||
{ ...componentOptions, container },
|
||||
);
|
||||
const tour = CoreComponentsRegistry.require(element, CoreUserToursUserTourComponent);
|
||||
const tour = CoreDirectivesRegistry.require(element, CoreUserToursUserTourComponent);
|
||||
|
||||
return this.startTour(tour, options.watch ?? (options as CoreUserToursFocusedOptions).focus);
|
||||
}
|
||||
|
|
|
@ -12,14 +12,16 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
import { CoreIframeUtils } from '@services/utils/iframe';
|
||||
|
||||
/**
|
||||
* Inject some scripts for iOS iframes.
|
||||
*/
|
||||
export default async function(): Promise<void> {
|
||||
await CorePlatform.ready();
|
||||
|
||||
if (!CoreApp.isIOS() || !('WKUserScript' in window)) {
|
||||
if (!CorePlatform.isIOS() || !('WKUserScript' in window)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import { CoreSiteError } from '@classes/errors/siteerror';
|
|||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||
import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config';
|
||||
import { CoreUserNullSupportConfig } from '@features/user/classes/support/null-support-config';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreCustomURLSchemes } from '@services/urlschemes';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
|
@ -27,6 +27,9 @@ import { CoreEvents } from '@singletons/events';
|
|||
|
||||
let lastInAppUrl: string | null = null;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export default function(): void {
|
||||
// Check URLs loaded in any InAppBrowser.
|
||||
CoreEvents.on(CoreEvents.IAB_LOAD_START, async (event) => {
|
||||
|
@ -59,7 +62,7 @@ export default function(): void {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!CoreApp.isAndroid()) {
|
||||
if (!CorePlatform.isAndroid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import { CoreDB } from '@services/db';
|
|||
import { CoreEvents } from '@singletons/events';
|
||||
import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';
|
||||
|
||||
import { makeSingleton, Keyboard, StatusBar, Device } from '@singletons';
|
||||
import { makeSingleton, Keyboard, StatusBar } from '@singletons';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreColors } from '@singletons/colors';
|
||||
import { DBNAME, SCHEMA_VERSIONS_TABLE_NAME, SCHEMA_VERSIONS_TABLE_SCHEMA, SchemaVersionsDBEntry } from '@services/database/app';
|
||||
|
@ -204,7 +204,7 @@ export class CoreAppProvider {
|
|||
return 'itms-apps://itunes.apple.com/app/' + storesConfig.ios;
|
||||
}
|
||||
|
||||
if (this.isAndroid() && storesConfig.android) {
|
||||
if (CorePlatform.isAndroid() && storesConfig.android) {
|
||||
return 'market://details?id=' + storesConfig.android;
|
||||
}
|
||||
|
||||
|
@ -219,13 +219,10 @@ export class CoreAppProvider {
|
|||
* Get platform major version number.
|
||||
*
|
||||
* @returns The platform major number.
|
||||
* @deprecated since 4.1.1. Use CorePlatform.getPlatformMajorVersion instead.
|
||||
*/
|
||||
getPlatformMajorVersion(): number {
|
||||
if (!CorePlatform.isMobile()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Number(Device.version?.split('.')[0]);
|
||||
return CorePlatform.getPlatformMajorVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -242,9 +239,10 @@ export class CoreAppProvider {
|
|||
* Checks if the app is running in an Android mobile or tablet device.
|
||||
*
|
||||
* @returns Whether the app is running in an Android mobile or tablet device.
|
||||
* @deprecated since 4.1.1. Use CorePlatform.isAndroid instead.
|
||||
*/
|
||||
isAndroid(): boolean {
|
||||
return CorePlatform.isMobile() && CorePlatform.is('android');
|
||||
return CorePlatform.isAndroid();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -261,9 +259,10 @@ export class CoreAppProvider {
|
|||
* Checks if the app is running in an iOS mobile or tablet device.
|
||||
*
|
||||
* @returns Whether the app is running in an iOS mobile or tablet device.
|
||||
* @deprecated since 4.1.1. Use CorePlatform.isIOS instead.
|
||||
*/
|
||||
isIOS(): boolean {
|
||||
return CorePlatform.isMobile() && !CorePlatform.is('android');
|
||||
return CorePlatform.isIOS();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -387,7 +386,7 @@ export class CoreAppProvider {
|
|||
*/
|
||||
openKeyboard(): void {
|
||||
// Open keyboard is not supported in desktop and in iOS.
|
||||
if (this.isAndroid()) {
|
||||
if (CorePlatform.isAndroid()) {
|
||||
Keyboard.show();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { FileEntry } from '@ionic-native/file/ngx';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreNetwork } from '@services/network';
|
||||
import { CoreFile } from '@services/file';
|
||||
import { CoreFilepool } from '@services/filepool';
|
||||
|
@ -31,6 +30,7 @@ import { CoreNetworkError } from '@classes/errors/network-error';
|
|||
import { CoreConfig } from './config';
|
||||
import { CoreCanceledError } from '@classes/errors/cancelederror';
|
||||
import { CoreMimetypeUtils } from '@services/utils/mimetype';
|
||||
import { CorePlatform } from './platform';
|
||||
|
||||
/**
|
||||
* Provider to provide some helper functions regarding files and packages.
|
||||
|
@ -44,7 +44,7 @@ export class CoreFileHelperProvider {
|
|||
* @returns Boolean.
|
||||
*/
|
||||
defaultIsOpenWithPicker(): boolean {
|
||||
return CoreApp.isIOS() && CoreConstants.CONFIG.iOSDefaultOpenFileAction === OpenFileAction.OPEN_WITH;
|
||||
return CorePlatform.isIOS() && CoreConstants.CONFIG.iOSDefaultOpenFileAction === OpenFileAction.OPEN_WITH;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,7 +16,6 @@ import { Injectable } from '@angular/core';
|
|||
|
||||
import { FileEntry, DirectoryEntry, Entry, Metadata, IFile } from '@ionic-native/file/ngx';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreMimetypeUtils } from '@services/utils/mimetype';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
|
@ -140,9 +139,9 @@ export class CoreFileProvider {
|
|||
|
||||
await CorePlatform.ready();
|
||||
|
||||
if (CoreApp.isAndroid()) {
|
||||
if (CorePlatform.isAndroid()) {
|
||||
this.basePath = File.externalApplicationStorageDirectory || this.basePath;
|
||||
} else if (CoreApp.isIOS()) {
|
||||
} else if (CorePlatform.isIOS()) {
|
||||
this.basePath = File.documentsDirectory || this.basePath;
|
||||
} else if (!this.isAvailable() || this.basePath === '') {
|
||||
this.logger.error('Error getting device OS.');
|
||||
|
@ -441,7 +440,7 @@ export class CoreFileProvider {
|
|||
*/
|
||||
calculateFreeSpace(): Promise<number> {
|
||||
return File.getFreeDiskSpace().then((size) => {
|
||||
if (CoreApp.isIOS()) {
|
||||
if (CorePlatform.isIOS()) {
|
||||
// In iOS the size is in bytes.
|
||||
return Number(size);
|
||||
}
|
||||
|
@ -717,7 +716,7 @@ export class CoreFileProvider {
|
|||
async getBasePathToDownload(): Promise<string> {
|
||||
await this.init();
|
||||
|
||||
if (CoreApp.isIOS()) {
|
||||
if (CorePlatform.isIOS()) {
|
||||
// In iOS we want the internal URL (cdvfile://localhost/persistent/...).
|
||||
const dirEntry = await File.resolveDirectoryUrl(this.basePath);
|
||||
|
||||
|
@ -1263,7 +1262,7 @@ export class CoreFileProvider {
|
|||
return src;
|
||||
}
|
||||
|
||||
if (CoreApp.isIOS()) {
|
||||
if (CorePlatform.isIOS()) {
|
||||
return src.replace(CoreConstants.CONFIG.ioswebviewscheme + '://localhost/_app_file_', 'file://');
|
||||
}
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ export class CoreGeolocationProvider {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!CoreApp.isIOS()) {
|
||||
if (!CorePlatform.isIOS()) {
|
||||
Diagnostic.switchToLocationSettings();
|
||||
await CoreApp.waitForResume(30000);
|
||||
|
||||
|
@ -142,7 +142,7 @@ export class CoreGeolocationProvider {
|
|||
* Request and return the location authorization status for the application.
|
||||
*/
|
||||
protected async requestLocationAuthorization(): Promise<void> {
|
||||
if (!CoreApp.isIOS()) {
|
||||
if (!CorePlatform.isIOS()) {
|
||||
await Diagnostic.requestLocationAuthorization();
|
||||
|
||||
return;
|
||||
|
|
|
@ -186,7 +186,7 @@ export class CoreLocalNotificationsProvider {
|
|||
*/
|
||||
canDisableSound(): boolean {
|
||||
// Only allow disabling sound in Android 7 or lower. In iOS and Android 8+ it can easily be done with system settings.
|
||||
return this.isAvailable() && CoreApp.isAndroid() && CoreApp.getPlatformMajorVersion() < 8;
|
||||
return CorePlatform.isAndroid() && CorePlatform.getPlatformMajorVersion() < 8;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -195,7 +195,7 @@ export class CoreLocalNotificationsProvider {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
protected async createDefaultChannel(): Promise<void> {
|
||||
if (!CoreApp.isAndroid()) {
|
||||
if (!CorePlatform.isAndroid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -583,7 +583,7 @@ export class CoreLocalNotificationsProvider {
|
|||
notification.data.component = component;
|
||||
notification.data.siteId = siteId;
|
||||
|
||||
if (CoreApp.isAndroid()) {
|
||||
if (CorePlatform.isAndroid()) {
|
||||
notification.icon = notification.icon || 'res://icon';
|
||||
notification.smallIcon = notification.smallIcon || 'res://smallicon';
|
||||
notification.color = notification.color || CoreConstants.CONFIG.notificoncolor;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Platform } from '@ionic/angular';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { Device, makeSingleton } from '@singletons';
|
||||
|
||||
/**
|
||||
* Extend Ionic's Platform service.
|
||||
|
@ -22,6 +22,55 @@ import { makeSingleton } from '@singletons';
|
|||
@Injectable({ providedIn: 'root' })
|
||||
export class CorePlatformService extends Platform {
|
||||
|
||||
/**
|
||||
* Get platform major version number.
|
||||
*
|
||||
* @returns The platform major number.
|
||||
*/
|
||||
getPlatformMajorVersion(): number {
|
||||
if (!this.isMobile()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Number(Device.version?.split('.')[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the app is running in an Android mobile or tablet device.
|
||||
*
|
||||
* @returns Whether the app is running in an Android mobile or tablet device.
|
||||
*/
|
||||
isAndroid(): boolean {
|
||||
return this.isMobile() && this.is('android');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the app is running in an iOS mobile or tablet device.
|
||||
*
|
||||
* @returns Whether the app is running in an iOS mobile or tablet device.
|
||||
*/
|
||||
isIOS(): boolean {
|
||||
return this.isMobile() && !this.is('android');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the app is running in an iPad device.
|
||||
*
|
||||
* @returns Whether the app is running in an iPad device.
|
||||
*/
|
||||
isIPad(): boolean {
|
||||
return this.isIOS() && this.is('ipad');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the app is running in an iPhone device.
|
||||
*
|
||||
* @returns Whether the app is running in an iPhone device.
|
||||
*/
|
||||
isIPhone(): boolean {
|
||||
return this.isIOS() && this.is('iphone');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the app is running in a mobile or tablet device (Cordova).
|
||||
*
|
||||
|
|
|
@ -12,11 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreTextUtilsProvider } from '@services/utils/text';
|
||||
import { DomSanitizer } from '@singletons';
|
||||
|
||||
import { mockSingleton } from '@/testing/utils';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
|
||||
describe('CoreTextUtilsProvider', () => {
|
||||
|
||||
|
@ -24,7 +24,7 @@ describe('CoreTextUtilsProvider', () => {
|
|||
let textUtils: CoreTextUtilsProvider;
|
||||
|
||||
beforeEach(() => {
|
||||
mockSingleton(CoreApp, [], { isAndroid: () => config.platform === 'android' });
|
||||
mockSingleton(CorePlatform, [], { isAndroid: () => config.platform === 'android' });
|
||||
mockSingleton(DomSanitizer, [], { bypassSecurityTrustUrl: url => url });
|
||||
|
||||
textUtils = new CoreTextUtilsProvider();
|
||||
|
@ -57,7 +57,7 @@ describe('CoreTextUtilsProvider', () => {
|
|||
expect(url).toEqual('geo:0,0?q=Moodle%20Spain%20HQ');
|
||||
|
||||
expect(DomSanitizer.bypassSecurityTrustUrl).toHaveBeenCalled();
|
||||
expect(CoreApp.isAndroid).toHaveBeenCalled();
|
||||
expect(CorePlatform.isAndroid).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('builds address URL for non-Android platforms', () => {
|
||||
|
@ -73,7 +73,7 @@ describe('CoreTextUtilsProvider', () => {
|
|||
expect(url).toEqual('http://maps.google.com?q=Moodle%20Spain%20HQ');
|
||||
|
||||
expect(DomSanitizer.bypassSecurityTrustUrl).toHaveBeenCalled();
|
||||
expect(CoreApp.isAndroid).toHaveBeenCalled();
|
||||
expect(CorePlatform.isAndroid).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('doesn\'t build address if it\'s already a URL', () => {
|
||||
|
|
|
@ -51,12 +51,13 @@ import { CoreSites } from '@services/sites';
|
|||
import { NavigationStart } from '@angular/router';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
import { CoreNetwork } from '@services/network';
|
||||
import { CoreSiteError } from '@classes/errors/siteerror';
|
||||
import { CoreUserSupport } from '@features/user/services/support';
|
||||
import { CoreErrorInfoComponent } from '@components/error-info/error-info';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
|
||||
/*
|
||||
* "Utils" service with helper functions for UI, DOM elements and HTML code.
|
||||
|
@ -133,7 +134,7 @@ export class CoreDomUtilsProvider {
|
|||
const getAvailableBytes = async (): Promise<number | null> => {
|
||||
const availableBytes = await CoreFile.calculateFreeSpace();
|
||||
|
||||
if (CoreApp.isAndroid()) {
|
||||
if (CorePlatform.isAndroid()) {
|
||||
return availableBytes;
|
||||
} else {
|
||||
// Space calculation is not accurate on iOS, but it gets more accurate when space is lower.
|
||||
|
@ -152,7 +153,7 @@ export class CoreDomUtilsProvider {
|
|||
} else {
|
||||
const availableSize = CoreTextUtils.bytesToSize(availableBytes, 2);
|
||||
|
||||
if (CoreApp.isAndroid() && size.size > availableBytes - CoreConstants.MINIMUM_FREE_SPACE) {
|
||||
if (CorePlatform.isAndroid() && size.size > availableBytes - CoreConstants.MINIMUM_FREE_SPACE) {
|
||||
throw new CoreError(
|
||||
Translate.instant(
|
||||
'core.course.insufficientavailablespace',
|
||||
|
@ -338,7 +339,7 @@ export class CoreDomUtilsProvider {
|
|||
|
||||
if (focusElement === document.activeElement) {
|
||||
await CoreUtils.nextTick();
|
||||
if (CoreApp.isAndroid() && this.supportsInputKeyboard(focusElement)) {
|
||||
if (CorePlatform.isAndroid() && this.supportsInputKeyboard(focusElement)) {
|
||||
// On some Android versions the keyboard doesn't open automatically.
|
||||
CoreApp.openKeyboard();
|
||||
}
|
||||
|
@ -665,10 +666,10 @@ export class CoreDomUtilsProvider {
|
|||
*
|
||||
* @param element The root element of the component/directive.
|
||||
* @returns The instance, undefined if not found.
|
||||
* @deprecated since 4.0.0. Use CoreComponentsRegistry instead.
|
||||
* @deprecated since 4.0.0. Use CoreDirectivesRegistry instead.
|
||||
*/
|
||||
getInstanceByElement<T = unknown>(element: Element): T | undefined {
|
||||
return CoreComponentsRegistry.resolve<T>(element) ?? undefined;
|
||||
return CoreDirectivesRegistry.resolve<T>(element) ?? undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1529,7 +1530,7 @@ export class CoreDomUtilsProvider {
|
|||
buttons: buttons,
|
||||
});
|
||||
|
||||
const isDevice = CoreApp.isAndroid() || CoreApp.isIOS();
|
||||
const isDevice = CorePlatform.isAndroid() || CorePlatform.isIOS();
|
||||
if (!isDevice) {
|
||||
// Treat all anchors so they don't override the app.
|
||||
const alertMessageEl: HTMLElement | null = alert.querySelector('.alert-message');
|
||||
|
@ -1718,10 +1719,10 @@ export class CoreDomUtilsProvider {
|
|||
*
|
||||
* @param element The root element of the component/directive.
|
||||
* @param instance The instance to store.
|
||||
* @deprecated since 4.0.0. Use CoreComponentsRegistry instead.
|
||||
* @deprecated since 4.0.0. Use CoreDirectivesRegistry instead.
|
||||
*/
|
||||
storeInstanceByElement(element: Element, instance: unknown): void {
|
||||
CoreComponentsRegistry.register(element, instance);
|
||||
CoreDirectivesRegistry.register(element, instance);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1989,7 +1990,7 @@ export class CoreDomUtilsProvider {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
async waitForResizeDone(windowWidth?: number, windowHeight?: number, retries = 0): Promise<void> {
|
||||
if (!CoreApp.isIOS()) {
|
||||
if (!CorePlatform.isIOS()) {
|
||||
return; // Only wait in iOS.
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ import { Injectable } from '@angular/core';
|
|||
import { WKUserScriptWindow } from 'cordova-plugin-wkuserscript';
|
||||
import { WKWebViewCookiesWindow } from 'cordova-plugin-wkwebview-cookies';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreNetwork } from '@services/network';
|
||||
import { CoreFile } from '@services/file';
|
||||
import { CoreFileHelper } from '@services/file-helper';
|
||||
|
@ -32,6 +31,7 @@ import { CoreWindow } from '@singletons/window';
|
|||
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
|
||||
import { CorePath } from '@singletons/path';
|
||||
import { CorePromisedValue } from '@classes/promised-value';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
|
||||
/**
|
||||
* Possible types of frame elements.
|
||||
|
@ -531,7 +531,7 @@ export class CoreIframeUtilsProvider {
|
|||
} catch (error) {
|
||||
CoreDomUtils.showErrorModal(error);
|
||||
}
|
||||
} else if (CoreApp.isIOS() && (!link.target || link.target == '_self') && element) {
|
||||
} else if (CorePlatform.isIOS() && (!link.target || link.target == '_self') && element) {
|
||||
// In cordova ios 4.1.0 links inside iframes stopped working. We'll manually treat them.
|
||||
event && event.preventDefault();
|
||||
if (element.tagName.toLowerCase() == 'object') {
|
||||
|
@ -564,7 +564,7 @@ export class CoreIframeUtilsProvider {
|
|||
* @returns Promise resolved when done.
|
||||
*/
|
||||
async fixIframeCookies(url: string): Promise<void> {
|
||||
if (!CoreApp.isIOS() || !url || CoreUrlUtils.isLocalFileUrl(url)) {
|
||||
if (!CorePlatform.isIOS() || !url || CoreUrlUtils.isLocalFileUrl(url)) {
|
||||
// No need to fix cookies.
|
||||
return;
|
||||
}
|
||||
|
@ -593,7 +593,7 @@ export class CoreIframeUtilsProvider {
|
|||
* @returns Boolean.
|
||||
*/
|
||||
shouldDisplayHelp(): boolean {
|
||||
return CoreApp.isIOS() && CoreApp.getPlatformMajorVersion() >= 14;
|
||||
return CorePlatform.isIOS() && CorePlatform.getPlatformMajorVersion() >= 14;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -25,6 +25,7 @@ import { CoreUtils } from '@services/utils/utils';
|
|||
import extToMime from '@/assets/exttomime.json';
|
||||
import mimeToExt from '@/assets/mimetoext.json';
|
||||
import { CoreFileEntry, CoreFileHelper } from '@services/file-helper';
|
||||
import { CoreUrl } from '@singletons/url';
|
||||
|
||||
interface MimeTypeInfo {
|
||||
type: string;
|
||||
|
@ -302,18 +303,13 @@ export class CoreMimetypeUtilsProvider {
|
|||
* @returns The lowercased extension without the dot, or undefined.
|
||||
*/
|
||||
guessExtensionFromUrl(fileUrl: string): string | undefined {
|
||||
const split = fileUrl.split('.');
|
||||
const split = CoreUrl.removeUrlAnchor(fileUrl).split('.');
|
||||
let extension: string | undefined;
|
||||
|
||||
if (split.length > 1) {
|
||||
let candidate = split[split.length - 1].toLowerCase();
|
||||
// Remove params if any.
|
||||
let position = candidate.indexOf('?');
|
||||
if (position > -1) {
|
||||
candidate = candidate.substring(0, position);
|
||||
}
|
||||
// Remove anchor if any.
|
||||
position = candidate.indexOf('#');
|
||||
const position = candidate.indexOf('?');
|
||||
if (position > -1) {
|
||||
candidate = candidate.substring(0, position);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ import { Injectable } from '@angular/core';
|
|||
import { SafeUrl } from '@angular/platform-browser';
|
||||
import { ModalOptions } from '@ionic/core';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreAnyError, CoreError } from '@classes/errors/error';
|
||||
import { DomSanitizer, makeSingleton, Translate } from '@singletons';
|
||||
import { CoreWSFile } from '@services/ws';
|
||||
|
@ -28,6 +27,7 @@ import { CoreText } from '@singletons/text';
|
|||
import { CoreUrl } from '@singletons/url';
|
||||
import { AlertButton } from '@ionic/angular';
|
||||
import { CorePath } from '@singletons/path';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
|
||||
/**
|
||||
* Different type of errors the app can treat.
|
||||
|
@ -187,7 +187,7 @@ export class CoreTextUtilsProvider {
|
|||
return DomSanitizer.bypassSecurityTrustUrl(address);
|
||||
}
|
||||
|
||||
return DomSanitizer.bypassSecurityTrustUrl((CoreApp.isAndroid() ? 'geo:0,0?q=' : 'http://maps.google.com?q=') +
|
||||
return DomSanitizer.bypassSecurityTrustUrl((CorePlatform.isAndroid() ? 'geo:0,0?q=' : 'http://maps.google.com?q=') +
|
||||
encodeURIComponent(address));
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import { CoreUrl } from '@singletons/url';
|
|||
import { CoreSites } from '@services/sites';
|
||||
import { CorePath } from '@singletons/path';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
import { CoreMedia } from '@singletons/media';
|
||||
|
||||
/*
|
||||
* "Utils" service with helper functions for URLs.
|
||||
|
@ -120,7 +121,8 @@ export class CoreUrlUtilsProvider {
|
|||
// Also, only use it for "core" pluginfile endpoints. Some plugins can implement their own endpoint (like customcert).
|
||||
return !CoreConstants.CONFIG.disableTokenFile && !!accessKey && !url.match(/[&?]file=/) && (
|
||||
url.indexOf(CorePath.concatenatePaths(siteUrl, 'pluginfile.php')) === 0 ||
|
||||
url.indexOf(CorePath.concatenatePaths(siteUrl, 'webservice/pluginfile.php')) === 0);
|
||||
url.indexOf(CorePath.concatenatePaths(siteUrl, 'webservice/pluginfile.php')) === 0) &&
|
||||
!CoreMedia.sourceUsesJavascriptPlayer({ src: url });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,7 +17,6 @@ import { InAppBrowserObject, InAppBrowserOptions } from '@ionic-native/in-app-br
|
|||
import { FileEntry } from '@ionic-native/file/ngx';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreFile } from '@services/file';
|
||||
import { CoreLang } from '@services/lang';
|
||||
|
@ -995,12 +994,12 @@ export class CoreUtilsProvider {
|
|||
const extension = CoreMimetypeUtils.getFileExtension(path);
|
||||
const mimetype = extension && CoreMimetypeUtils.getMimeType(extension);
|
||||
|
||||
if (mimetype == 'text/html' && CoreApp.isAndroid()) {
|
||||
if (mimetype == 'text/html' && CorePlatform.isAndroid()) {
|
||||
// Open HTML local files in InAppBrowser, in system browser some embedded files aren't loaded.
|
||||
this.openInApp(path);
|
||||
|
||||
return;
|
||||
} else if (extension === 'apk' && CoreApp.isAndroid()) {
|
||||
} else if (extension === 'apk' && CorePlatform.isAndroid()) {
|
||||
const url = await CoreUtils.ignoreErrors(
|
||||
CoreFilepool.getFileUrlByPath(CoreSites.getCurrentSiteId(), CoreFile.removeBasePath(path)),
|
||||
);
|
||||
|
@ -1065,7 +1064,7 @@ export class CoreUtilsProvider {
|
|||
options.enableViewPortScale = options.enableViewPortScale ?? 'yes'; // Enable zoom on iOS by default.
|
||||
options.allowInlineMediaPlayback = options.allowInlineMediaPlayback ?? 'yes'; // Allow playing inline videos in iOS.
|
||||
|
||||
if (!options.location && CoreApp.isIOS() && url.indexOf('file://') === 0) {
|
||||
if (!options.location && CorePlatform.isIOS() && url.indexOf('file://') === 0) {
|
||||
// The URL uses file protocol, don't show it on iOS.
|
||||
// In Android we keep it because otherwise we lose the whole toolbar.
|
||||
options.location = 'no';
|
||||
|
@ -1190,7 +1189,7 @@ export class CoreUtilsProvider {
|
|||
* @returns Promise resolved when opened.
|
||||
*/
|
||||
async openOnlineFile(url: string): Promise<void> {
|
||||
if (CoreApp.isAndroid()) {
|
||||
if (CorePlatform.isAndroid()) {
|
||||
// In Android we need the mimetype to open it.
|
||||
const mimetype = await this.ignoreErrors(this.getMimeTypeFromUrl(url));
|
||||
|
||||
|
@ -1823,7 +1822,7 @@ export class CoreUtilsProvider {
|
|||
shouldOpenWithDialog(options: CoreUtilsOpenFileOptions = {}): boolean {
|
||||
const openFileAction = options.iOSOpenFileAction ?? CoreConstants.CONFIG.iOSDefaultOpenFileAction;
|
||||
|
||||
return CoreApp.isIOS() && openFileAction == OpenFileAction.OPEN_WITH;
|
||||
return CorePlatform.isIOS() && openFileAction == OpenFileAction.OPEN_WITH;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,12 +13,14 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { AsyncComponent } from '@classes/async-component';
|
||||
import { AsyncDirective } from '@classes/async-directive';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreLogger } from './logger';
|
||||
|
||||
/**
|
||||
* Registry to keep track of component instances.
|
||||
*
|
||||
* @deprecated since 4.1.1. Use CoreDirectivesRegistry instead.
|
||||
*/
|
||||
export class CoreComponentsRegistry {
|
||||
|
||||
|
@ -74,7 +76,7 @@ export class CoreComponentsRegistry {
|
|||
* @param componentClass Component class.
|
||||
* @returns Promise resolved when done.
|
||||
*/
|
||||
static async waitComponentReady<T extends AsyncComponent>(
|
||||
static async waitComponentReady<T extends AsyncDirective>(
|
||||
element: Element | null,
|
||||
componentClass?: ComponentConstructor<T>,
|
||||
): Promise<void> {
|
||||
|
@ -96,7 +98,7 @@ export class CoreComponentsRegistry {
|
|||
* @param componentClass Component class.
|
||||
* @returns Promise resolved when done.
|
||||
*/
|
||||
static async waitComponentsReady<T extends AsyncComponent>(
|
||||
static async waitComponentsReady<T extends AsyncDirective>(
|
||||
element: Element,
|
||||
selector: string,
|
||||
componentClass?: ComponentConstructor<T>,
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Directive } from '@angular/core';
|
||||
import { AsyncDirective } from '@classes/async-directive';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreLogger } from './logger';
|
||||
|
||||
/**
|
||||
* Registry to keep track of directive instances.
|
||||
*/
|
||||
export class CoreDirectivesRegistry {
|
||||
|
||||
private static instances: WeakMap<Element, unknown[]> = new WeakMap();
|
||||
protected static logger = CoreLogger.getInstance('CoreDirectivesRegistry');
|
||||
|
||||
/**
|
||||
* Register a directive instance.
|
||||
*
|
||||
* @param element Root element.
|
||||
* @param instance Directive instance.
|
||||
*/
|
||||
static register(element: Element, instance: unknown): void {
|
||||
const list = this.instances.get(element) ?? [];
|
||||
list.push(instance);
|
||||
this.instances.set(element, list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a directive instance.
|
||||
*
|
||||
* @param element Root element.
|
||||
* @param directiveClass Directive class.
|
||||
* @returns Directive instance.
|
||||
*/
|
||||
static resolve<T>(element?: Element | null, directiveClass?: DirectiveConstructor<T>): T | null {
|
||||
const list = (element && this.instances.get(element) as T[]) ?? [];
|
||||
|
||||
return list.find(instance => !directiveClass || instance instanceof directiveClass) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve all directive instances.
|
||||
*
|
||||
* @param element Root element.
|
||||
* @param directiveClass Directive class.
|
||||
* @returns Directive instances.
|
||||
*/
|
||||
static resolveAll<T>(element?: Element | null, directiveClass?: DirectiveConstructor<T>): T[] {
|
||||
const list = (element && this.instances.get(element) as T[]) ?? [];
|
||||
|
||||
return list.filter(instance => !directiveClass || instance instanceof directiveClass) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a directive instance and fail if it cannot be resolved.
|
||||
*
|
||||
* @param element Root element.
|
||||
* @param directiveClass Directive class.
|
||||
* @returns Directive instance.
|
||||
*/
|
||||
static require<T>(element: Element, directiveClass?: DirectiveConstructor<T>): T {
|
||||
const instance = this.resolve(element, directiveClass);
|
||||
|
||||
if (!instance) {
|
||||
throw new Error('Couldn\'t resolve directive instance');
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a directive instance and wait to be ready.
|
||||
*
|
||||
* @param element Root element.
|
||||
* @param directiveClass Directive class.
|
||||
* @returns Promise resolved when done.
|
||||
*/
|
||||
static async waitDirectiveReady<T extends AsyncDirective>(
|
||||
element: Element | null,
|
||||
directiveClass?: DirectiveConstructor<T>,
|
||||
): Promise<void> {
|
||||
const instance = this.resolve(element, directiveClass);
|
||||
if (!instance) {
|
||||
this.logger.error('No instance registered for element ' + directiveClass, element);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await instance.ready();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all directive instances and wait to be ready.
|
||||
*
|
||||
* @param element Root element.
|
||||
* @param directiveClass Directive class.
|
||||
* @returns Promise resolved when done.
|
||||
*/
|
||||
static async waitDirectivesReady<T extends AsyncDirective>(
|
||||
element: Element,
|
||||
selector?: string,
|
||||
directiveClass?: DirectiveConstructor<T>,
|
||||
): Promise<void> {
|
||||
let elements: Element[] = [];
|
||||
|
||||
if (!selector || element.matches(selector)) {
|
||||
// Element to wait is myself.
|
||||
elements = [element];
|
||||
} else {
|
||||
elements = Array.from(element.querySelectorAll(selector));
|
||||
}
|
||||
|
||||
if (!elements.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(elements.map(async element => {
|
||||
const instances = this.resolveAll<T>(element, directiveClass);
|
||||
|
||||
await Promise.all(instances.map(instance => instance.ready()));
|
||||
}));
|
||||
|
||||
// Wait for next tick to ensure directives are completely rendered.
|
||||
await CoreUtils.nextTick();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Directive constructor.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type DirectiveConstructor<T = Directive> = { new(...args: any[]): T };
|
|
@ -585,3 +585,11 @@ export type CoreScrollOptions = {
|
|||
addYAxis?: number;
|
||||
addXAxis?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Source of a media element.
|
||||
*/
|
||||
export type CoreMediaSource = {
|
||||
src: string;
|
||||
type?: string;
|
||||
};
|
||||
|
|
|
@ -20,6 +20,7 @@ import { CoreFilepoolComponentFileEventData } from '@services/filepool';
|
|||
import { CoreRedirectPayload } from '@services/navigator';
|
||||
import { CoreCourseModuleCompletionData } from '@features/course/services/course-helper';
|
||||
import { CoreScreenOrientation } from '@services/screen';
|
||||
import { VideoJSPlayer } from 'video.js';
|
||||
|
||||
/**
|
||||
* Observer instance to stop listening to an event.
|
||||
|
@ -64,6 +65,7 @@ export interface CoreEventsData {
|
|||
[CoreEvents.ORIENTATION_CHANGE]: CoreEventOrientationData;
|
||||
[CoreEvents.COURSE_MODULE_VIEWED]: CoreEventCourseModuleViewed;
|
||||
[CoreEvents.COMPLETE_REQUIRED_PROFILE_DATA_FINISHED]: CoreEventCompleteRequiredProfileDataFinished;
|
||||
[CoreEvents.JS_PLAYER_CREATED]: CoreEventJSVideoPlayerCreated;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -108,7 +110,7 @@ export class CoreEvents {
|
|||
static readonly FILE_SHARED = 'file_shared';
|
||||
static readonly KEYBOARD_CHANGE = 'keyboard_change';
|
||||
/**
|
||||
* @deprecated since app 4.0. Use CoreComponentsRegistry promises instead.
|
||||
* @deprecated since app 4.0. Use CoreDirectivesRegistry promises instead.
|
||||
*/
|
||||
static readonly CORE_LOADING_CHANGED = 'core_loading_changed';
|
||||
static readonly ORIENTATION_CHANGE = 'orientation_change';
|
||||
|
@ -123,6 +125,7 @@ export class CoreEvents {
|
|||
static readonly COMPLETE_REQUIRED_PROFILE_DATA_FINISHED = 'complete_required_profile_data_finished';
|
||||
static readonly MAIN_HOME_LOADED = 'main_home_loaded';
|
||||
static readonly FULL_SCREEN_CHANGED = 'full_screen_changed';
|
||||
static readonly JS_PLAYER_CREATED = 'js_player_created';
|
||||
|
||||
protected static logger = CoreLogger.getInstance('CoreEvents');
|
||||
protected static observables: { [eventName: string]: Subject<unknown> } = {};
|
||||
|
@ -490,3 +493,12 @@ export type CoreEventCourseModuleViewed = {
|
|||
export type CoreEventCompleteRequiredProfileDataFinished = {
|
||||
path: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Data passed to JS_PLAYER_CREATED event.
|
||||
*/
|
||||
export type CoreEventJSVideoPlayerCreated = {
|
||||
id: string;
|
||||
element: HTMLAudioElement | HTMLVideoElement;
|
||||
player: VideoJSPlayer;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { CorePlatform } from '@services/platform';
|
||||
import { CoreMimetypeUtils } from '@services/utils/mimetype';
|
||||
|
||||
/**
|
||||
* Singleton with helper functions for media.
|
||||
*/
|
||||
export class CoreMedia {
|
||||
|
||||
// Avoid creating singleton instances.
|
||||
private constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all source URLs and types for a video or audio.
|
||||
*
|
||||
* @param mediaElement Audio or video element.
|
||||
* @returns List of sources.
|
||||
*/
|
||||
static getMediaSources(mediaElement: HTMLVideoElement | HTMLAudioElement): CoreMediaSource[] {
|
||||
const sources = Array.from(mediaElement.querySelectorAll('source')).map(source => ({
|
||||
src: source.src || source.getAttribute('target-src') || '',
|
||||
type: source.type,
|
||||
}));
|
||||
|
||||
if (mediaElement.src) {
|
||||
sources.push({
|
||||
src: mediaElement.src,
|
||||
type: '',
|
||||
});
|
||||
}
|
||||
|
||||
return sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a source needs to be converted to be able to reproduce it.
|
||||
*
|
||||
* @param source Source.
|
||||
* @returns Whether needs conversion.
|
||||
*/
|
||||
static sourceNeedsConversion(source: CoreMediaSource): boolean {
|
||||
if (!CorePlatform.isIOS()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let extension = source.type ? CoreMimetypeUtils.getExtension(source.type) : undefined;
|
||||
if (!extension) {
|
||||
extension = CoreMimetypeUtils.guessExtensionFromUrl(source.src);
|
||||
}
|
||||
|
||||
return !!extension && ['ogv', 'webm', 'oga', 'ogg'].includes(extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if JS player should be used for a certain source.
|
||||
*
|
||||
* @param source Source.
|
||||
* @returns Whether JS player should be used.
|
||||
*/
|
||||
static sourceUsesJavascriptPlayer(source: CoreMediaSource): boolean {
|
||||
// For now, only use JS player if the source needs to be converted.
|
||||
return CoreMedia.sourceNeedsConversion(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if JS player should be used for a certain audio or video.
|
||||
*
|
||||
* @param mediaElement Media element.
|
||||
* @returns Whether JS player should be used.
|
||||
*/
|
||||
static mediaUsesJavascriptPlayer(mediaElement: HTMLVideoElement | HTMLAudioElement): boolean {
|
||||
if (!CorePlatform.isIOS()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const sources = CoreMedia.getMediaSources(mediaElement);
|
||||
|
||||
return sources.some(source => CoreMedia.sourceUsesJavascriptPlayer(source));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Source of a media element.
|
||||
*/
|
||||
export type CoreMediaSource = {
|
||||
src: string;
|
||||
type?: string;
|
||||
};
|
|
@ -1,104 +0,0 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { wait } from '@/testing/utils';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
|
||||
const cssClassName = 'core-components-registry-test';
|
||||
const createAndRegisterInstance = () => {
|
||||
const element = document.createElement('div');
|
||||
element.classList.add(cssClassName);
|
||||
const instance = new ComponentsRegistryTestClass();
|
||||
|
||||
CoreComponentsRegistry.register(element, instance);
|
||||
|
||||
return { element, instance };
|
||||
};
|
||||
|
||||
describe('CoreComponentsRegistry singleton', () => {
|
||||
|
||||
let element: HTMLElement;
|
||||
let testClassInstance: ComponentsRegistryTestClass;
|
||||
|
||||
beforeEach(() => {
|
||||
const result = createAndRegisterInstance();
|
||||
element = result.element;
|
||||
testClassInstance = result.instance;
|
||||
});
|
||||
|
||||
it('resolves stored instances', () => {
|
||||
expect(CoreComponentsRegistry.resolve(element)).toEqual(testClassInstance);
|
||||
expect(CoreComponentsRegistry.resolve(element, ComponentsRegistryTestClass)).toEqual(testClassInstance);
|
||||
expect(CoreComponentsRegistry.resolve(element, CoreComponentsRegistry)).toEqual(null);
|
||||
expect(CoreComponentsRegistry.resolve(document.createElement('div'))).toEqual(null);
|
||||
});
|
||||
|
||||
it('requires stored instances', () => {
|
||||
expect(CoreComponentsRegistry.require(element)).toEqual(testClassInstance);
|
||||
expect(CoreComponentsRegistry.require(element, ComponentsRegistryTestClass)).toEqual(testClassInstance);
|
||||
expect(() => CoreComponentsRegistry.require(element, CoreComponentsRegistry)).toThrow();
|
||||
expect(() => CoreComponentsRegistry.require(document.createElement('div'))).toThrow();
|
||||
});
|
||||
|
||||
it('waits for component ready', async () => {
|
||||
expect(testClassInstance.isReady).toBe(false);
|
||||
|
||||
await CoreComponentsRegistry.waitComponentReady(element);
|
||||
|
||||
expect(testClassInstance.isReady).toBe(true);
|
||||
});
|
||||
|
||||
it('waits for components ready: just one', async () => {
|
||||
expect(testClassInstance.isReady).toBe(false);
|
||||
|
||||
await CoreComponentsRegistry.waitComponentsReady(element, `.${cssClassName}`);
|
||||
|
||||
expect(testClassInstance.isReady).toBe(true);
|
||||
});
|
||||
|
||||
it('waits for components ready: multiple', async () => {
|
||||
const secondResult = createAndRegisterInstance();
|
||||
const thirdResult = createAndRegisterInstance();
|
||||
thirdResult.element.classList.remove(cssClassName); // Remove the class so the element and instance aren't treated.
|
||||
|
||||
const parent = document.createElement('div');
|
||||
parent.appendChild(element);
|
||||
parent.appendChild(secondResult.element);
|
||||
parent.appendChild(thirdResult.element);
|
||||
|
||||
expect(testClassInstance.isReady).toBe(false);
|
||||
expect(secondResult.instance.isReady).toBe(false);
|
||||
expect(thirdResult.instance.isReady).toBe(false);
|
||||
|
||||
await CoreComponentsRegistry.waitComponentsReady(parent, `.${cssClassName}`);
|
||||
|
||||
expect(testClassInstance.isReady).toBe(true);
|
||||
expect(secondResult.instance.isReady).toBe(true);
|
||||
expect(thirdResult.instance.isReady).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
class ComponentsRegistryTestClass {
|
||||
|
||||
randomId = Math.random();
|
||||
isReady = false;
|
||||
|
||||
async ready(): Promise<void> {
|
||||
await wait(50);
|
||||
|
||||
this.isReady = true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { wait } from '@/testing/utils';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
|
||||
const cssClassName = 'core-directives-registry-test';
|
||||
const createAndRegisterInstance = (element?: HTMLElement) => {
|
||||
element = element ?? document.createElement('div');
|
||||
element.classList.add(cssClassName);
|
||||
const instance = new DirectivesRegistryTestClass();
|
||||
|
||||
CoreDirectivesRegistry.register(element, instance);
|
||||
|
||||
return { element, instance };
|
||||
};
|
||||
|
||||
describe('CoreDirectivesRegistry singleton', () => {
|
||||
|
||||
let element: HTMLElement;
|
||||
let testClassInstance: DirectivesRegistryTestClass;
|
||||
let testClassSecondInstance: DirectivesRegistryTestClass;
|
||||
let testAltClassInstance: DirectivesRegistryAltTestClass;
|
||||
|
||||
beforeEach(() => {
|
||||
let result = createAndRegisterInstance();
|
||||
element = result.element;
|
||||
testClassInstance = result.instance;
|
||||
|
||||
result = createAndRegisterInstance(element);
|
||||
testClassSecondInstance = result.instance;
|
||||
|
||||
testAltClassInstance = new DirectivesRegistryAltTestClass();
|
||||
CoreDirectivesRegistry.register(element, testAltClassInstance);
|
||||
});
|
||||
|
||||
it('resolves a stored instance', () => {
|
||||
expect(CoreDirectivesRegistry.resolve(element)).toEqual(testClassInstance);
|
||||
expect(CoreDirectivesRegistry.resolve(element, DirectivesRegistryTestClass)).toEqual(testClassInstance);
|
||||
expect(CoreDirectivesRegistry.resolve(element, DirectivesRegistryAltTestClass)).toEqual(testAltClassInstance);
|
||||
expect(CoreDirectivesRegistry.resolve(element, CoreDirectivesRegistry)).toEqual(null);
|
||||
expect(CoreDirectivesRegistry.resolve(document.createElement('div'))).toEqual(null);
|
||||
});
|
||||
|
||||
it('resolves all stored instances', () => {
|
||||
expect(CoreDirectivesRegistry.resolveAll(element)).toEqual(
|
||||
[testClassInstance, testClassSecondInstance, testAltClassInstance],
|
||||
);
|
||||
expect(CoreDirectivesRegistry.resolveAll(element, DirectivesRegistryTestClass)).toEqual(
|
||||
[testClassInstance, testClassSecondInstance],
|
||||
);
|
||||
expect(CoreDirectivesRegistry.resolveAll(element, DirectivesRegistryAltTestClass)).toEqual([testAltClassInstance]);
|
||||
expect(CoreDirectivesRegistry.resolveAll(element, CoreDirectivesRegistry)).toEqual([]);
|
||||
expect(CoreDirectivesRegistry.resolveAll(document.createElement('div'))).toEqual([]);
|
||||
});
|
||||
|
||||
it('requires a stored instance', () => {
|
||||
expect(CoreDirectivesRegistry.require(element)).toEqual(testClassInstance);
|
||||
expect(CoreDirectivesRegistry.require(element, DirectivesRegistryTestClass)).toEqual(testClassInstance);
|
||||
expect(CoreDirectivesRegistry.require(element, DirectivesRegistryAltTestClass)).toEqual(testAltClassInstance);
|
||||
expect(() => CoreDirectivesRegistry.require(element, CoreDirectivesRegistry)).toThrow();
|
||||
expect(() => CoreDirectivesRegistry.require(document.createElement('div'))).toThrow();
|
||||
});
|
||||
|
||||
it('waits for directive ready', async () => {
|
||||
expect(testClassInstance.isReady).toBe(false);
|
||||
|
||||
await CoreDirectivesRegistry.waitDirectiveReady(element);
|
||||
|
||||
expect(testClassInstance.isReady).toBe(true);
|
||||
});
|
||||
|
||||
it('waits for directives ready: just one element and directive', async () => {
|
||||
const result = createAndRegisterInstance();
|
||||
expect(result.instance.isReady).toBe(false);
|
||||
|
||||
await CoreDirectivesRegistry.waitDirectivesReady(result.element, `.${cssClassName}`);
|
||||
|
||||
expect(result.instance.isReady).toBe(true);
|
||||
expect(testClassInstance.isReady).toBe(false);
|
||||
});
|
||||
|
||||
it('waits for directives ready: all directives, single element', async () => {
|
||||
expect(testClassInstance.isReady).toBe(false);
|
||||
expect(testClassSecondInstance.isReady).toBe(false);
|
||||
expect(testAltClassInstance.isReady).toBe(false);
|
||||
|
||||
await CoreDirectivesRegistry.waitDirectivesReady(element);
|
||||
|
||||
expect(testClassInstance.isReady).toBe(true);
|
||||
expect(testClassSecondInstance.isReady).toBe(true);
|
||||
expect(testAltClassInstance.isReady).toBe(true);
|
||||
});
|
||||
|
||||
it('waits for directives ready: filter by class, single element', async () => {
|
||||
expect(testClassInstance.isReady).toBe(false);
|
||||
expect(testClassSecondInstance.isReady).toBe(false);
|
||||
expect(testAltClassInstance.isReady).toBe(false);
|
||||
|
||||
await CoreDirectivesRegistry.waitDirectivesReady(element, `.${cssClassName}`, DirectivesRegistryTestClass);
|
||||
|
||||
expect(testClassInstance.isReady).toBe(true);
|
||||
expect(testClassSecondInstance.isReady).toBe(true);
|
||||
expect(testAltClassInstance.isReady).toBe(false);
|
||||
});
|
||||
|
||||
it('waits for directives ready: multiple elements', async () => {
|
||||
const secondResult = createAndRegisterInstance();
|
||||
const thirdResult = createAndRegisterInstance();
|
||||
thirdResult.element.classList.remove(cssClassName); // Remove the class so the element and instance aren't treated.
|
||||
|
||||
const parent = document.createElement('div');
|
||||
parent.appendChild(element);
|
||||
parent.appendChild(secondResult.element);
|
||||
parent.appendChild(thirdResult.element);
|
||||
|
||||
expect(testClassInstance.isReady).toBe(false);
|
||||
expect(testClassSecondInstance.isReady).toBe(false);
|
||||
expect(testAltClassInstance.isReady).toBe(false);
|
||||
expect(secondResult.instance.isReady).toBe(false);
|
||||
expect(thirdResult.instance.isReady).toBe(false);
|
||||
|
||||
await CoreDirectivesRegistry.waitDirectivesReady(parent, `.${cssClassName}`, DirectivesRegistryTestClass);
|
||||
|
||||
expect(testClassInstance.isReady).toBe(true);
|
||||
expect(testClassSecondInstance.isReady).toBe(true);
|
||||
expect(testAltClassInstance.isReady).toBe(false);
|
||||
expect(secondResult.instance.isReady).toBe(true);
|
||||
expect(thirdResult.instance.isReady).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
class DirectivesRegistryTestClass {
|
||||
|
||||
randomId = Math.random();
|
||||
isReady = false;
|
||||
|
||||
async ready(): Promise<void> {
|
||||
await wait(50);
|
||||
|
||||
this.isReady = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DirectivesRegistryAltTestClass {
|
||||
|
||||
randomId = Math.random();
|
||||
isReady = false;
|
||||
|
||||
async ready(): Promise<void> {
|
||||
await wait(50);
|
||||
|
||||
this.isReady = true;
|
||||
}
|
||||
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
<meta name="msapplication-tap-highlight" content="no" />
|
||||
|
||||
<link rel="icon" type="image/png" href="assets/icon/favicon.png" />
|
||||
<link rel="stylesheet" href="assets/lib/video.js/video-js.min.css">
|
||||
|
||||
<!-- add to homescreen for ios -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
|
|
|
@ -23,7 +23,7 @@ import { CoreNetwork, CoreNetworkService } from '@services/network';
|
|||
import { CorePushNotifications, CorePushNotificationsProvider } from '@features/pushnotifications/services/pushnotifications';
|
||||
import { CoreCronDelegate, CoreCronDelegateService } from '@services/cron';
|
||||
import { CoreLoadingComponent } from '@components/loading/loading';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreSites, CoreSitesProvider } from '@services/sites';
|
||||
|
@ -127,7 +127,7 @@ export class TestingBehatRuntimeService {
|
|||
.filter((element) => CoreDom.isElementVisible(element));
|
||||
|
||||
await Promise.all(elements.map(element =>
|
||||
CoreComponentsRegistry.waitComponentReady(element, CoreLoadingComponent)));
|
||||
CoreDirectivesRegistry.waitDirectiveReady(element, CoreLoadingComponent)));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -62,6 +62,8 @@ const DEFAULT_SERVICE_SINGLETON_MOCKS: [CoreSingletonProxy, unknown][] = [
|
|||
[CorePlatform, mock({
|
||||
is: () => false,
|
||||
isMobile: () => false,
|
||||
isAndroid: () => false,
|
||||
isIOS: () => false,
|
||||
ready: () => Promise.resolve(),
|
||||
resume: new Subject<void>(),
|
||||
})],
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* VideoJS modifications.
|
||||
**/
|
||||
|
||||
/**
|
||||
* App specific modifications.
|
||||
**/
|
||||
|
||||
// In case of error reset height to the default (otherwise no aspect ratio is available and height becomes 0).
|
||||
.video-js.vjs-error {
|
||||
height: 150px !important;
|
||||
}
|
||||
|
||||
// Fake full screen mode.
|
||||
.vjs-ios-moodleapp-fs {
|
||||
z-index: 100000 !important;
|
||||
|
||||
canvas {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-bottom: var(--ion-safe-area-bottom, 0px);
|
||||
}
|
||||
|
||||
.vjs-control-bar {
|
||||
height: calc(3em + var(--ion-safe-area-bottom, 0px));
|
||||
|
||||
.vjs-progress-control {
|
||||
padding-bottom: var(--ion-safe-area-bottom, 0px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Styles extracted from:
|
||||
* https://github.com/moodle/moodle/blob/3c5423d8c0bae003e6eb20119ca657c0c6760309/media/player/videojs/styles.css#L1773
|
||||
**/
|
||||
|
||||
/* Audio: Remove big play button (leave only the button in controls). */
|
||||
.video-js.vjs-audio .vjs-big-play-button {
|
||||
display: none;
|
||||
}
|
||||
/* Audio: Make the controlbar visible by default */
|
||||
.video-js.vjs-audio .vjs-control-bar {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
/* Make player height minimum to the controls height so when we hide video/poster area the controls are displayed correctly. */
|
||||
.video-js.vjs-audio {
|
||||
min-height: 3em;
|
||||
}
|
||||
/* In case of error reset height to the default (otherwise no aspect ratio is available and height becomes 0). */
|
||||
.video-js.vjs-error {
|
||||
height: 150px;
|
||||
}
|
||||
/* Minimum height for videos should not be less than the size of play button. */
|
||||
.mediaplugin_videojs video {
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
/* MDL-61020: VideoJS timeline progress bar should not be flipped in RTL mode. */
|
||||
|
||||
/* Prevent the progress bar from being flipped in RTL. */
|
||||
/*rtl:ignore*/
|
||||
.video-js .vjs-progress-holder .vjs-play-progress,
|
||||
.video-js .vjs-progress-holder .vjs-load-progress,
|
||||
.video-js .vjs-progress-holder .vjs-load-progress div {
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
/* Keep the video scrubber button at the end of the progress bar in RTL. */
|
||||
/*rtl:ignore*/
|
||||
.video-js .vjs-play-progress:before {
|
||||
left: auto;
|
||||
right: -0.5em;
|
||||
}
|
||||
/* Prevent the volume slider from being flipped in RTL. */
|
||||
/*rtl:ignore*/
|
||||
.video-js .vjs-volume-level {
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
/* Keep the volume slider handle at the end of the volume slider in RTL. */
|
||||
/*rtl:ignore*/
|
||||
.vjs-slider-horizontal .vjs-volume-level:before {
|
||||
left: auto;
|
||||
right: -0.5em;
|
||||
}
|
|
@ -26,6 +26,7 @@
|
|||
@import "./components/rubrics.scss";
|
||||
@import "./components/mod-label.scss";
|
||||
@import "../core/components/error-info/error-info.scss";
|
||||
@import "./components/videojs.scss";
|
||||
|
||||
/* Some styles from 3rd party libraries. */
|
||||
@import "./bootstrap.scss";
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
declare module 'video.js' {
|
||||
function videojs(
|
||||
elementOrId: string | HTMLElement,
|
||||
options?: VideoJSOptions,
|
||||
readyCallback?: () => void,
|
||||
): VideoJSPlayer;
|
||||
|
||||
namespace videojs {
|
||||
function getPlayer(id: string): VideoJSPlayer | null;
|
||||
function log(...args): void;
|
||||
function getComponent(name: string): any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
}
|
||||
|
||||
export default videojs;
|
||||
|
||||
export type VideoJSPlayer = {
|
||||
play: () => Promise<void>;
|
||||
pause: () => Promise<void>;
|
||||
on: (name: string, callback: (ev: Event) => void) => void;
|
||||
off: (name: string, callback: (ev: Event) => void) => void;
|
||||
dispose: () => void;
|
||||
el: () => HTMLElement;
|
||||
fluid: (val?: boolean) => void | boolean;
|
||||
isFullscreen: (val?: boolean) => void | boolean;
|
||||
videoHeight: () => number;
|
||||
videoWidth: () => number;
|
||||
currentDimensions: () => { width: number; height: number };
|
||||
dimension: (dimension: string, value: number) => void;
|
||||
};
|
||||
|
||||
export type VideoJSOptions = {
|
||||
aspectRatio?: string;
|
||||
audioOnlyMode?: boolean;
|
||||
audioPosterMode?: boolean;
|
||||
autoplay?: boolean | string;
|
||||
autoSetup?: boolean;
|
||||
base?: string;
|
||||
breakpoints?: Record<string, number>;
|
||||
children?: string[] | Record<string, Record<string, unknown>>;
|
||||
controlBar?: {
|
||||
fullscreenToggle?: boolean;
|
||||
pictureInPictureToggle?: boolean;
|
||||
remainingTimeDisplay?: {
|
||||
displayNegative?: boolean;
|
||||
};
|
||||
};
|
||||
controls?: boolean;
|
||||
fluid?: boolean;
|
||||
fullscreen?: {
|
||||
options?: Record<string, unknown>;
|
||||
};
|
||||
height?: string | number;
|
||||
id?: string;
|
||||
inactivityTimeout?: number;
|
||||
language?: string;
|
||||
languages?: Record<string, Record<string, string>>;
|
||||
liveui?: boolean;
|
||||
liveTracker?: {
|
||||
trackingThreshold?: number;
|
||||
liveTolerance?: number;
|
||||
};
|
||||
loop?: boolean;
|
||||
muted?: boolean;
|
||||
nativeControlsForTouch?: boolean;
|
||||
normalizeAutoplay?: boolean;
|
||||
notSupportedMessage?: string;
|
||||
noUITitleAttributes?: boolean;
|
||||
playbackRates?: number[];
|
||||
plugins?: Record<string, Record<string, unknown>>;
|
||||
poster?: string;
|
||||
preferFullWindow?: boolean;
|
||||
preload?: PreloadOption;
|
||||
responsive?: boolean;
|
||||
restoreEl?: boolean | HTMLElement;
|
||||
source?: TechSourceObject;
|
||||
sources?: TechSourceObject[];
|
||||
src?: string;
|
||||
suppressNotSupportedError?: boolean;
|
||||
tag?: HTMLElement;
|
||||
techCanOverridePoster?: boolean;
|
||||
techOrder?: string[];
|
||||
userActions?: {
|
||||
click?: boolean | ((ev: MouseEvent) => void);
|
||||
doubleClick?: boolean | ((ev: MouseEvent) => void);
|
||||
hotkeys?: boolean | ((ev: KeyboardEvent) => void) | {
|
||||
fullscreenKey?: (ev: KeyboardEvent) => void;
|
||||
muteKey?: (ev: KeyboardEvent) => void;
|
||||
playPauseKey?: (ev: KeyboardEvent) => void;
|
||||
};
|
||||
};
|
||||
'vtt.js'?: string;
|
||||
width?: string | number;
|
||||
};
|
||||
|
||||
export type TechSourceObject = {
|
||||
src: string; // Source URL.
|
||||
type: string; // Mimetype.
|
||||
};
|
||||
|
||||
export type PreloadOption = '' | 'none' | 'metadata' | 'auto';
|
||||
}
|
Loading…
Reference in New Issue