multi_embed_player 3.0.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/.github/workflows/build-and-deploy.yml +44 -0
  2. package/.gitmodules +3 -0
  3. package/CLAUDE.md +92 -0
  4. package/README.md +0 -24
  5. package/add_types.sh +61 -0
  6. package/browserExtention/chrome/background.js +55 -0
  7. package/browserExtention/chrome/extention.json +1 -0
  8. package/browserExtention/chrome/liteplayer.js +26439 -0
  9. package/browserExtention/chrome/manifest.json +31 -0
  10. package/browserExtention/chrome/player-selector.js +1854 -0
  11. package/browserExtention/firefox/background.js +27 -0
  12. package/browserExtention/firefox/extention.json +1 -0
  13. package/browserExtention/firefox/liteplayer.js +26439 -0
  14. package/browserExtention/firefox/manifest.json +19 -0
  15. package/browserExtention/firefox/player-selector.js +1854 -0
  16. package/documents/.hugo_build.lock +0 -0
  17. package/documents/archetypes/default.md +5 -0
  18. package/documents/assets/jsconfig.json +11 -0
  19. package/documents/content/docs/install.md +103 -0
  20. package/documents/content/docs/quickstart.md +51 -0
  21. package/documents/content/docs/reference/HTML.md +31 -0
  22. package/documents/content/docs/reference/_index.md +10 -0
  23. package/documents/content/docs/reference/error_code.md +23 -0
  24. package/documents/content/docs/reference/iframe_api.md +737 -0
  25. package/documents/content/docs/reference/iframe_class.md +230 -0
  26. package/documents/content/docs/reference/multi_embed_player_class.md +113 -0
  27. package/documents/content/docs/reference/reserved_words.md +71 -0
  28. package/documents/content/docs/usage/GDPR_mode.md +77 -0
  29. package/documents/content/docs/usage/_index.md +10 -0
  30. package/documents/content/docs/usage/custom_playlist.md +239 -0
  31. package/documents/content/docs/usage/embed_api.md +163 -0
  32. package/documents/content/docs/usage/embed_various_service.md +81 -0
  33. package/documents/content/docs/usage/thumbnail_click.md +57 -0
  34. package/documents/go.mod +8 -0
  35. package/documents/go.sum +14 -0
  36. package/documents/hugo.toml +18 -0
  37. package/documents/layouts/partials/docs/sidebar.html +117 -0
  38. package/documents/layouts/partials/landing/features.html +47 -0
  39. package/documents/layouts/robots.txt +4 -0
  40. package/documents/static/_headers +7 -0
  41. package/documents/static/localStorageCheck.html +27 -0
  42. package/documents/static/no_extention.json +1 -0
  43. package/example.html +27 -0
  44. package/extention.json +1 -0
  45. package/icon/video_not_found.odg +0 -0
  46. package/icon/video_not_found.svgz +0 -0
  47. package/iframe_api/bilibili.ts +1095 -0
  48. package/iframe_api/niconico.ts +429 -0
  49. package/iframe_api/soundcloud.ts +450 -0
  50. package/iframe_api/youtube.ts +311 -0
  51. package/multi_embed_player.ts +989 -0
  52. package/package.json +10 -41
  53. package/player_api_gate/bilibili-api-gate/cgi/cpp/bilibili-api-gate-cgi.cpp +281 -0
  54. package/player_api_gate/bilibili-api-gate/cgi/go/src.go +46 -0
  55. package/player_api_gate/bilibili-api-gate/cloudflare_workers/package-lock.json +1356 -0
  56. package/player_api_gate/bilibili-api-gate/cloudflare_workers/package.json +12 -0
  57. package/player_api_gate/bilibili-api-gate/cloudflare_workers/src/index.js +50 -0
  58. package/player_api_gate/bilibili-api-gate/cloudflare_workers/wrangler.toml +3 -0
  59. package/player_api_gate/iframe-api-ts/.editorconfig +12 -0
  60. package/player_api_gate/iframe-api-ts/.prettierrc +6 -0
  61. package/player_api_gate/iframe-api-ts/package-lock.json +3054 -0
  62. package/player_api_gate/iframe-api-ts/package.json +18 -0
  63. package/player_api_gate/iframe-api-ts/src/bilibili.ts +49 -0
  64. package/player_api_gate/iframe-api-ts/src/index.ts +35 -0
  65. package/player_api_gate/iframe-api-ts/src/niconico.ts +95 -0
  66. package/player_api_gate/iframe-api-ts/src/soundcloud.ts +38 -0
  67. package/player_api_gate/iframe-api-ts/src/types.ts +115 -0
  68. package/player_api_gate/iframe-api-ts/src/url-proxy.ts +29 -0
  69. package/player_api_gate/iframe-api-ts/src/utils.ts +82 -0
  70. package/player_api_gate/iframe-api-ts/src/youtube.ts +41 -0
  71. package/player_api_gate/iframe-api-ts/test/bilibili.spec.ts +47 -0
  72. package/player_api_gate/iframe-api-ts/test/env.d.ts +3 -0
  73. package/player_api_gate/iframe-api-ts/test/index.spec.ts +59 -0
  74. package/player_api_gate/iframe-api-ts/test/niconico.spec.ts +55 -0
  75. package/player_api_gate/iframe-api-ts/test/soundcloud.spec.ts +55 -0
  76. package/player_api_gate/iframe-api-ts/test/tsconfig.json +8 -0
  77. package/player_api_gate/iframe-api-ts/test/url-proxy.spec.ts +46 -0
  78. package/player_api_gate/iframe-api-ts/test/youtube.spec.ts +45 -0
  79. package/player_api_gate/iframe-api-ts/tsconfig.json +45 -0
  80. package/player_api_gate/iframe-api-ts/vitest.config.mts +11 -0
  81. package/player_api_gate/iframe-api-ts/worker-configuration.d.ts +5768 -0
  82. package/player_api_gate/iframe-api-ts/wrangler.jsonc +47 -0
  83. package/player_api_gate/iframe_api/.editorconfig +13 -0
  84. package/player_api_gate/iframe_api/.prettierrc +6 -0
  85. package/player_api_gate/iframe_api/package-lock.json +1307 -0
  86. package/player_api_gate/iframe_api/package.json +12 -0
  87. package/player_api_gate/iframe_api/src/bilibili_api.js +60 -0
  88. package/player_api_gate/iframe_api/src/index.js +47 -0
  89. package/player_api_gate/iframe_api/src/niconico_api.js +112 -0
  90. package/player_api_gate/iframe_api/src/soundcloud_api.js +57 -0
  91. package/player_api_gate/iframe_api/src/url_proxy.js +28 -0
  92. package/player_api_gate/iframe_api/src/youtube_api.js +44 -0
  93. package/player_api_gate/iframe_api/wrangler.toml +51 -0
  94. package/player_api_gate/niconico-imager/cgi/go/src.go +74 -0
  95. package/player_api_gate/niconico-imager/cloudflare_workers/package-lock.json +2175 -0
  96. package/player_api_gate/niconico-imager/cloudflare_workers/package.json +12 -0
  97. package/player_api_gate/niconico-imager/cloudflare_workers/src/index.js +78 -0
  98. package/player_api_gate/niconico-imager/cloudflare_workers/wrangler.toml +3 -0
  99. package/test_script.html +172 -0
  100. package/tsconfig.json +36 -0
  101. package/dist/iframe_api/bilibili.d.ts +0 -91
  102. package/dist/iframe_api/bilibili.d.ts.map +0 -1
  103. package/dist/iframe_api/bilibili.js +0 -451
  104. package/dist/iframe_api/bilibili.js.map +0 -1
  105. package/dist/iframe_api/index.d.ts +0 -6
  106. package/dist/iframe_api/index.d.ts.map +0 -1
  107. package/dist/iframe_api/index.js +0 -8
  108. package/dist/iframe_api/index.js.map +0 -1
  109. package/dist/iframe_api/niconico.d.ts +0 -42
  110. package/dist/iframe_api/niconico.d.ts.map +0 -1
  111. package/dist/iframe_api/niconico.js +0 -181
  112. package/dist/iframe_api/niconico.js.map +0 -1
  113. package/dist/iframe_api/soundcloud.d.ts +0 -80
  114. package/dist/iframe_api/soundcloud.d.ts.map +0 -1
  115. package/dist/iframe_api/soundcloud.js +0 -188
  116. package/dist/iframe_api/soundcloud.js.map +0 -1
  117. package/dist/iframe_api/youtube.d.ts +0 -133
  118. package/dist/iframe_api/youtube.d.ts.map +0 -1
  119. package/dist/iframe_api/youtube.js +0 -278
  120. package/dist/iframe_api/youtube.js.map +0 -1
  121. package/dist/multi_embed_player.d.ts +0 -48
  122. package/dist/multi_embed_player.d.ts.map +0 -1
  123. package/dist/multi_embed_player.js +0 -318
  124. package/dist/multi_embed_player.js.map +0 -1
  125. package/dist/types.d.ts +0 -126
  126. package/dist/types.d.ts.map +0 -1
  127. package/dist/types.js +0 -22
  128. package/dist/types.js.map +0 -1
@@ -0,0 +1,989 @@
1
+ // 型定義
2
+ type ServiceType = 'youtube' | 'niconico' | 'bilibili' | 'soundcloud';
3
+
4
+ interface ApiPromiseData {
5
+ res: Array<(value: any) => void>;
6
+ rej: Array<(reason?: any) => void>;
7
+ }
8
+
9
+ interface PlaylistItem {
10
+ service: ServiceType;
11
+ videoId: string;
12
+ call_array: any[];
13
+ call_index: number;
14
+ startSeconds?: number;
15
+ endSeconds?: number;
16
+ subService?: ServiceType;
17
+ subVideoId?: string;
18
+ [key: string]: any;
19
+ }
20
+
21
+ // multi_embed_player クラス用の型定義
22
+ type ServiceStatusMap = Record<ServiceType, number>;
23
+ type ServiceApiCache = Record<ServiceType, Record<string, any>>;
24
+ type ServiceApiPromise = Record<ServiceType, Record<string, ApiPromiseData>>;
25
+ type ServiceBooleanMap = Record<ServiceType, boolean>;
26
+ type ServiceUrlMap = Record<ServiceType, string>;
27
+ type IframeApiClassMap = Record<string, any>;
28
+
29
+ declare var multi_embed_player_set_variable: any;
30
+ declare var YT: any;
31
+
32
+ /**
33
+ * Fetches the iframe API for a given service and video ID.
34
+ * @param {string} service - The name of the service.
35
+ * @param {string} videoid - The ID of the video.
36
+ * @param {boolean} use_cors - Whether to use CORS.
37
+ * @param {boolean} image_proxy - The image proxy.
38
+ * @param {boolean} GDPR_access_accept - Whether GDPR access is accepted.
39
+ * @param {boolean} failed_send_error - Whether to send an error if the request fails.
40
+ * @param {HTMLElement} failed_send_error_target - The target to send the error to.
41
+ * @returns {Promise}
42
+ */
43
+ const multi_embed_player_fetch_iframe_api = async(service: ServiceType, videoid: string, use_cors: boolean, image_proxy: boolean, GDPR_access_accept: boolean, failed_send_error: boolean = false, failed_send_error_target: HTMLElement | null = null): Promise<void> => {
44
+ const xml_first_search = (data: string, search_string: string, start: number = 0): string => {
45
+ return data.substring(data.indexOf("<"+search_string+">",start)+search_string.length+2,data.indexOf("</"+search_string+">",start))
46
+ }
47
+ const possible_direct_access = GDPR_access_accept&&multi_embed_player.possible_direct_access_services.includes(service);
48
+ if(use_cors||possible_direct_access){
49
+ let url = "";
50
+ if(possible_direct_access){
51
+ url = "";
52
+ }
53
+ else if(multi_embed_player.cors_proxy!==""){
54
+ url = multi_embed_player.cors_proxy;
55
+ }
56
+ else{
57
+ url = `${multi_embed_player.iframe_api_endpoint}?route=url_proxy&url=`;
58
+ }
59
+ let first_access = false;
60
+ if(multi_embed_player.api_promise[service][videoid]===undefined){
61
+ multi_embed_player.api_promise[service][videoid] = {res:[],rej:[]};
62
+ first_access = true;
63
+ }
64
+ else{
65
+ await new Promise<void>((resolve,reject)=>{
66
+ const promiseData = multi_embed_player.api_promise[service][videoid];
67
+ if (promiseData) {
68
+ promiseData.res.push(resolve);
69
+ promiseData.rej.push(reject);
70
+ }
71
+ });
72
+ }
73
+ try{
74
+ if(first_access){
75
+ switch(service){
76
+ case 'soundcloud':
77
+ const numericRegex = /^[0-9]+$/;
78
+ let url_oembed;
79
+ if(numericRegex.test(videoid)){
80
+ url_oembed = `https://soundcloud.com/oembed?url=https://api.soundcloud.com/tracks/${videoid}&format=json`;
81
+ }
82
+ else{
83
+ url_oembed = `https://soundcloud.com/oembed?url=https://soundcloud.com/${videoid}&format=json`;
84
+ }
85
+ const oembed_response_fetch = await fetch(url + encodeURI(url_oembed));
86
+ let oembed_response = await oembed_response_fetch.json();
87
+ oembed_response["image_base64"] = url + oembed_response["thumbnail_url"];
88
+ multi_embed_player.api_cache[service][videoid] = oembed_response;
89
+ break;
90
+ case 'niconico':
91
+ const xml_response = await(await fetch(url + `https://ext.nicovideo.jp/api/getthumbinfo/${videoid}`)).text();
92
+ let image_url = xml_first_search(xml_response,"thumbnail_url");
93
+ let predict_long = 43+2*(videoid.length-2);
94
+ let return_data: Record<string, any> = {};
95
+ if(image_url.length>predict_long){
96
+ image_url += ".L";
97
+ }
98
+ if(image_url=="<?xml version="){
99
+ return_data["status"] = "invalid videoid";
100
+ return_data["thumbnail_url"] = "";
101
+ }
102
+ else{
103
+ return_data["status"] = "success";
104
+ return_data["thumbnail_url"] = image_url;
105
+ const search_element_names: Record<string, string> = {video_id:"video_id",title:"title",description:"description",length:"length",view_counter:"view_count",comment_num:"comment_count",mylist_counter:"mylist_count",first_retrieve:"publish_time",embeddable:"embedable",genre:"genre"};
106
+ Object.keys(search_element_names).forEach(key_name=>{
107
+ const mappedKey = search_element_names[key_name];
108
+ if (mappedKey) {
109
+ return_data[mappedKey] = xml_first_search(xml_response,key_name);
110
+ }
111
+ });
112
+ }
113
+ multi_embed_player.api_cache[service][videoid] = return_data;
114
+ break;
115
+ case 'bilibili':
116
+ let json_response_bilibili = await(await fetch(url + `https://api.bilibili.com/x/web-interface/view?bvid=${videoid}`)).json();
117
+ if(json_response_bilibili?.data?.pic===undefined){
118
+ json_response_bilibili["image_base64"] = null;
119
+ }
120
+ else{
121
+ json_response_bilibili["image_base64"] = url + json_response_bilibili.data.pic;
122
+ }
123
+ multi_embed_player.api_cache[service][videoid] = json_response_bilibili;
124
+ break;
125
+ case "youtube":
126
+ try{
127
+ let json_response_youtube = await(await fetch(url + `https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=${videoid}&format=json`)).json();
128
+ json_response_youtube["image_base64"] = url + json_response_youtube["thumbnail_url"];
129
+ multi_embed_player.api_cache[service][videoid] = json_response_youtube;
130
+ }
131
+ catch{
132
+ multi_embed_player.api_cache[service][videoid] = {};
133
+ }
134
+ break;
135
+ }
136
+ multi_embed_player.api_promise[service][videoid].res.forEach((resolve: (value: any) => void)=>resolve(undefined));
137
+ }
138
+ }
139
+ catch{
140
+ const promiseData = multi_embed_player.api_promise[service][videoid];
141
+ if(promiseData && Object.keys(promiseData).includes("rej")){
142
+ promiseData.rej.forEach((reject: (reason?: any) => void)=>reject());
143
+ }
144
+ if(failed_send_error&&failed_send_error_target!=null){
145
+ failed_send_error_target.dispatchEvent(new CustomEvent("onError",{detail:{code:1100}}));
146
+ }
147
+ multi_embed_player.api_cache[service][videoid] = {};
148
+ }
149
+ }
150
+ else{
151
+ let fetch_response;
152
+ try{
153
+ const url = `${multi_embed_player.iframe_api_endpoint}?route=${service}&videoid=${videoid}` + (image_proxy?"&image_base64=1":"");
154
+ if(multi_embed_player.api_promise[service][videoid]===undefined){
155
+ multi_embed_player.api_promise[service][videoid] = {res:[],rej:[]};
156
+ fetch_response = await fetch(url);
157
+ multi_embed_player.api_cache[service][videoid] = await fetch_response.json();
158
+ multi_embed_player.api_promise[service][videoid].res.forEach((resolve: (value: any) => void)=>resolve(undefined));
159
+ }
160
+ else{
161
+ await new Promise<void>((resolve,reject)=>{
162
+ const promiseData = multi_embed_player.api_promise[service][videoid];
163
+ if (promiseData) {
164
+ promiseData.res.push(resolve);
165
+ promiseData.rej.push(reject);
166
+ }
167
+ });
168
+ }
169
+ }
170
+ catch(e){
171
+ const promiseData = multi_embed_player.api_promise[service][videoid];
172
+ if(promiseData && Object.keys(promiseData).includes("rej")){
173
+ promiseData.rej.forEach((reject: (reason?: any) => void)=>reject());
174
+ }
175
+ if(failed_send_error&&failed_send_error_target!=null){
176
+ failed_send_error_target.dispatchEvent(new CustomEvent("onError",{detail:{code:1100}}));
177
+ }
178
+ else{
179
+ multi_embed_player.api_cache[service][videoid] = {};
180
+ }
181
+ }
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Resets all values in multi_embed_player.GDPR_accepted to false and removes the corresponding item from localStorage.
187
+ */
188
+ const multi_embed_player_GDPR_accepted_all_back_down = ()=>{
189
+ Object.keys(multi_embed_player.GDPR_accepted).forEach(key=>multi_embed_player.GDPR_accepted[key as ServiceType] = false);
190
+ localStorage.removeItem('multi_embed_player_GDPR_accepted');
191
+ }
192
+
193
+ /**
194
+ * A custom HTML element for embedding multiple video services in a single player.
195
+ * @extends HTMLElement
196
+ */
197
+ class multi_embed_player extends HTMLElement{
198
+ videoid: string | null;
199
+ follow_GDPR: boolean;
200
+ service: ServiceType | null = null;
201
+ image_url: string | null = null;
202
+ picture_tag: HTMLPictureElement | null = null;
203
+ player: any = null;
204
+ playlist: PlaylistItem[] = [];
205
+ autoplay: boolean = false;
206
+ error_not_declare: boolean = false;
207
+ previousData: PlaylistItem | null = null;
208
+ startSeconds: number = 0;
209
+ endSeconds: number = -1;
210
+ // static script_origin = "https://cdn.jsdelivr.net/gh/bonjinnorenka/multi_embed_player@v2/";
211
+ static script_origin = "http://localhost:5500/dist/";
212
+ static iframe_api_endpoint = "https://iframe_api.ryokuryu.workers.dev";
213
+ static mep_status_load_api: ServiceStatusMap = {youtube:0,niconico:0,bilibili:0,soundcloud:0};
214
+ static mep_load_api_promise: Record<'youtube' | 'niconico' | 'bilibili' | 'soundcloud', (() => void)[]> = {youtube:[],niconico:[],bilibili:[],soundcloud:[]};
215
+ static api_cache: ServiceApiCache = {niconico:{},bilibili:{},soundcloud:{},youtube:{}};
216
+ static api_promise: ServiceApiPromise = {niconico:{},bilibili:{},soundcloud:{},youtube:{}};
217
+ static GDPR_accept_promise: Record<'youtube' | 'niconico' | 'bilibili' | 'soundcloud', (() => void)[]> = {youtube:[],niconico:[],bilibili:[],soundcloud:[]};
218
+ static iframe_api_class: IframeApiClassMap = {};
219
+ static GDPR_accepted: ServiceBooleanMap = {youtube:false,niconico:false,bilibili:false,soundcloud:false};
220
+ static possible_direct_access_services: ServiceType[] = ["youtube","soundcloud"];
221
+ static cors_proxy: string = "";//if cors_proxy is not empty string,it use instead of iframe_api_endpoint and follow gdpr
222
+ static tearms_policy_service: ServiceUrlMap = {"youtube":"https://www.youtube.com/t/terms","niconico":"https://account.nicovideo.jp/rules/account?language=en-us","bilibili":"https://www.bilibili.com/blackboard/protocal/activity-lc1L-pIoh.html","soundcloud":"https://soundcloud.com/pages/privacy"};
223
+ static follow_GDPR: boolean = false;
224
+ constructor(){
225
+ super();
226
+ this.videoid = null;
227
+ this.follow_GDPR = multi_embed_player.follow_GDPR;
228
+ }
229
+ async connectedCallback(){
230
+ if(this.getAttribute("follow_GDPR")==="true"){
231
+ this.follow_GDPR = true;
232
+ }
233
+ if(this.getAttribute("type")===null||this.getAttribute("type")==="embed"||this.getAttribute("type")==="thumbnail-click"){
234
+ this.videoid = this.getAttribute("videoid");
235
+ this.service = this.getAttribute("service") as ServiceType | null;
236
+ if(this.getAttribute("img_url")!=null){
237
+ this.image_url = this.getAttribute("img_url");
238
+ }
239
+ else if(this.getAttribute("picture_tag")!=null){
240
+ this.picture_tag = document.createElement("picture");
241
+ this.appendChild(this.picture_tag);
242
+ this.picture_tag.innerHTML = this.getAttribute("picture_tag") || "";
243
+ }
244
+ else{
245
+ if (this.videoid && this.service) {
246
+ this.image_url = await this.#mep_imageurl(this.videoid, this.service);
247
+ } else {
248
+ this.image_url = null;
249
+ }
250
+ if(this.image_url && !await this.#check_image_status(this.image_url)){
251
+ const subVideoid = this.getAttribute("subVideoid");
252
+ const subService = this.getAttribute("subService") as ServiceType;
253
+ if (subVideoid && subService) {
254
+ this.image_url = await this.#mep_imageurl(subVideoid, subService);
255
+ } else {
256
+ this.image_url = null;
257
+ }
258
+ if(this.image_url && !await this.#check_image_status(this.image_url)){
259
+ this.style.backgroundImage = `${(window as any).multi_embed_player.script_origin}icon/video_not_found.svgz`;
260
+ }
261
+ }
262
+ }
263
+ if(typeof this.image_url === "string"){
264
+ this.style.backgroundImage = `url(${this.image_url})`;
265
+ }
266
+ else{
267
+ this.style.backgroundImage = `url(${(window as any).multi_embed_player.script_origin}icon/video_not_found.svgz)`;
268
+ }
269
+ //status setting
270
+ if(this.getAttribute("type")===null||this.getAttribute("type")==="embed"){
271
+ this.addEventListener('click', this.#add_iframe,{once:true});
272
+ }
273
+ if(this.getAttribute("type")==="thumbnail-click"){
274
+ this.addEventListener('click',()=>{this.#PlayOnPlayer(this.getAttribute("for") || "",this.getAttribute("service") || "",this.getAttribute("videoid") || "",this.getAttribute("start"),this.getAttribute("end"),this.getAttribute("subService"),this.getAttribute("subVideoid"))});
275
+ this.addEventListener('contextmenu',(e)=>{e.preventDefault();this.#addPlaylist()});
276
+ }
277
+ }
278
+ else if(this.getAttribute("type")==="player"){
279
+ this.player = {};
280
+ this.player.service = "before embed";
281
+ this.playlist = [];
282
+ this.addEventListener("onEndVideo",()=>{if(this.playlist.length>0){this.loadVideoById(this.playlist.shift())}});//終わりが来たとき次のやつを再生
283
+ this.addEventListener("addPlaylist",()=>{if(this.getPlayerState()===-1||this.getPlayerState()===4){if(this.playlist.length>0){this.loadVideoById(this.playlist.shift())}}});
284
+ }
285
+ }
286
+ /**
287
+ * Checks the status of an image URL.
288
+ * @async
289
+ * @param {string} img_url - The URL of the image to check.
290
+ * @returns {Promise<boolean>} - A promise that resolves to true if the image loads successfully, false otherwise.
291
+ */
292
+ async #check_image_status(img_url: string): Promise<boolean> {
293
+ if(typeof img_url !== "string"){
294
+ return false;
295
+ }
296
+ const img = new Image();
297
+ img.src = img_url;
298
+ return new Promise((resolve,reject)=>{img.onload = ()=>{img.remove();resolve(true)};img.onerror = ()=>{img.remove();resolve(false)}});
299
+ }
300
+ /**
301
+ * This function adds an iframe to the current element.
302
+ * @param {Event} e - The event that triggered the function. Default is null.
303
+ * @param {boolean} sub - A flag to indicate whether the iframe is a subframe. Default is false.
304
+ */
305
+ async #add_iframe(e: Event | null = null, sub: boolean = false): Promise<void> {
306
+ let content = new mep_playitem(this.getAttribute("service"),this.getAttribute("videoid"));
307
+ if(this.getAttribute("start")!=null){
308
+ content.startSeconds = Number(this.getAttribute("start"));
309
+ }
310
+ if(this.getAttribute("end")!=null){
311
+ content.endSeconds = Number(this.getAttribute("end"));
312
+ }
313
+ if(this.getAttribute("subvideoid")!=null&&this.getAttribute("subservice")!=null){
314
+ content.subVideoid = this.getAttribute("subvideoid") || undefined;
315
+ content.subService = this.getAttribute("subservice") as ServiceType;
316
+ }
317
+ this.player = {};
318
+ this.loadVideoById(content.toData());
319
+ }
320
+ /**
321
+ * Asynchronously fetches the image URL for a given video ID and service.
322
+ * @param {string} videoid - The ID of the video to fetch the image for.
323
+ * @param {string} service - The service to fetch the image from.
324
+ * @param {string} [filetype=null] - The type of file to fetch.
325
+ * @returns {Promise<string>} - A promise that resolves with the image URL.
326
+ */
327
+ async #mep_imageurl(videoid: string, service: ServiceType, filetype: string | null = null): Promise<string> {//必ずawaitを使って叩くこと
328
+ let GDPR_accepted = false;
329
+ if (!this.follow_GDPR) {
330
+ GDPR_accepted = true;
331
+ } else if (this.follow_GDPR && !(window as any).multi_embed_player.GDPR_accepted[service]) {
332
+ GDPR_accepted = false;
333
+ } else if (this.follow_GDPR && (window as any).multi_embed_player.GDPR_accepted[service]) {
334
+ GDPR_accepted = true;
335
+ }
336
+ let image_url = "";
337
+ let use_cors = false;
338
+ if(GDPR_accepted){
339
+ if((window as any).multi_embed_player.cors_proxy!==""){
340
+ image_url = (window as any).multi_embed_player.cors_proxy;
341
+ use_cors= true;
342
+ }
343
+ else{
344
+ image_url = `${(window as any).multi_embed_player.iframe_api_endpoint}?route=url_proxy&url=`;
345
+ }
346
+ }
347
+ if((window as any).multi_embed_player.cors_proxy!==""){
348
+ use_cors= true;
349
+ }
350
+ if(!GDPR_accepted||service==="bilibili"){//if follow gdpr or bilibili(bilibili don't allow to fetch thumbnail from crossorigin)
351
+ if(!(videoid in (window as any).multi_embed_player.api_cache[service])){
352
+ await multi_embed_player_fetch_iframe_api(service,videoid,use_cors,true,false);
353
+ }
354
+ return (window as any).multi_embed_player.api_cache[service][videoid]["image_base64"];
355
+ }
356
+ /*else if(service==="niconico"){
357
+ if(!(videoid in (window as any).multi_embed_player.api_cache[service])){
358
+ await this.fetch_iframe_api(service,videoid,use_cors,false,GDPR_accepted);
359
+ }
360
+ return image_url + (window as any).multi_embed_player.api_cache[service][videoid]["image"];
361
+ }*/
362
+ else if(service==="soundcloud"||service==="youtube"||service==="niconico"){
363
+ if(!(videoid in (window as any).multi_embed_player.api_cache[service])){
364
+ await multi_embed_player_fetch_iframe_api(service,videoid,use_cors,!GDPR_accepted,GDPR_accepted);
365
+ }
366
+ if(!GDPR_accepted){
367
+ return (window as any).multi_embed_player.api_cache[service][videoid]["image_base64"];
368
+ }
369
+ else{
370
+ return (window as any).multi_embed_player.api_cache[service][videoid]["thumbnail_url"];
371
+ }
372
+ }
373
+ else{
374
+ image_url = "invalid_url";
375
+ return image_url
376
+ }
377
+
378
+ }
379
+ /**
380
+ * Asynchronously accepts GDPR for a given service.
381
+ * @param {string} service - The name of the service to accept GDPR for.
382
+ * @returns {Promise<void>} - A promise that resolves when GDPR is accepted.
383
+ */
384
+ async #GDPR_accept(service: ServiceType): Promise<void>{
385
+ return new Promise<void>(async(resolve: () => void, reject: () => void)=>{
386
+ if(this.follow_GDPR){
387
+ if((window as any).multi_embed_player.GDPR_accepted[service]){
388
+ resolve();
389
+ }
390
+ else{
391
+ (window as any).multi_embed_player.GDPR_accept_promise[service].push(resolve);
392
+ const GDPR_check_div = document.createElement("div");
393
+ const firest_p_element = document.createElement("p");
394
+ firest_p_element.innerText = "This content is hosted by a third party.\nBy showing the external content you accept the terms and conditions";
395
+ GDPR_check_div.appendChild(firest_p_element);
396
+ const tearms_link = document.createElement("a");
397
+ tearms_link.href = (window as any).multi_embed_player.tearms_policy_service[service];
398
+ tearms_link.target = "_blank";
399
+ tearms_link.innerText = `${service} terms and conditions`;
400
+ GDPR_check_div.appendChild(tearms_link);
401
+ const second_p_element = document.createElement("p");
402
+ second_p_element.innerText = `service hosted by ${service} is not under our control and can change without notice.\nIf you notice any change, please let us know.\nAlso this accept status save for this domain localstorage if you accept and can access for this.`;
403
+ GDPR_check_div.appendChild(second_p_element);
404
+ const button_agree = document.createElement("button");
405
+ button_agree.innerText = "I accept";
406
+ button_agree.addEventListener("click",()=>{multi_embed_player_GDPR_reviever(service)});
407
+ GDPR_check_div.appendChild(button_agree);
408
+ //remove all children of this
409
+ while(this.firstChild){
410
+ this.removeChild(this.firstChild);
411
+ }
412
+ this.appendChild(GDPR_check_div);
413
+ this.style.backgroundImage = "";
414
+ }
415
+ }
416
+ else{
417
+ resolve();
418
+ }
419
+ });
420
+ }
421
+ /**
422
+ * Loads a video by its ID and sets the autoplay and subtitle options.
423
+ * @async
424
+ * @param {Object} data - The data object containing the video ID, service, start time, and end time.
425
+ * @param {boolean} [autoplay=true] - Whether or not to autoplay the video.
426
+ * @param {boolean} [sub=false] - Whether or not to load a subtitle. deprecated
427
+ * @returns {Promise<void>}
428
+ */
429
+ async loadVideoById(data: any, autoplay: boolean = true, sub: boolean = false): Promise<void> {
430
+ this.autoplay = autoplay;
431
+ if(this.player!=undefined){
432
+ let service_changed = false;
433
+ this.error_not_declare = false;
434
+ if(data===null||(Array.isArray(data.call_array) && typeof data.call_index==="number")){
435
+ if(data!==null){
436
+ this.previousData = data;
437
+ }
438
+ else{
439
+ data = this.previousData;
440
+ }
441
+ data = Object.assign({},data,data.call_array[data.call_index]);
442
+ this.videoid = data.videoId;
443
+ this.service = data.service;
444
+ if(data.call_array.length-1 < data.call_index){
445
+ console.error("too large call_index");
446
+ }
447
+ else if(data.call_array.length-1 === data.call_index){
448
+ this.error_not_declare = false;
449
+ }
450
+ else{
451
+ this.error_not_declare = true;
452
+ this.addEventListener("executeSecound",()=>{this.loadVideoById(null,autoplay,false)},{once:true});
453
+ }
454
+ if(this.service!==this.player.service){
455
+ this.#deleteEvent();
456
+ service_changed = true;
457
+ }
458
+ if (this.previousData) {
459
+ this.previousData.call_index++;
460
+ }
461
+ }
462
+ else{
463
+ if(sub==false){//1回目
464
+ this.previousData = data;
465
+ if(typeof data.subVideoId==="string"&&typeof data.subService==="string"){
466
+ this.error_not_declare = true;
467
+ this.addEventListener("executeSecound",()=>{this.loadVideoById(null,autoplay,false)},{once:true});
468
+ }
469
+ if(data.service!=this.player.service){
470
+ this.#deleteEvent();
471
+ service_changed = true;
472
+ this.service = data.service;
473
+ }
474
+ this.videoid = data.videoId;
475
+ }
476
+ else if(sub==true){
477
+ data = this.previousData;
478
+ this.#deleteEvent();
479
+ service_changed = true;
480
+ this.videoid = data.subVideoId;
481
+ this.service = data.subService;
482
+ }
483
+ }
484
+ await this.#GDPR_accept(data.service);
485
+ this.startSeconds = 0;
486
+ if(data.startSeconds!=undefined){
487
+ this.startSeconds = Number(data.startSeconds);
488
+ }
489
+ this.endSeconds = -1;
490
+ if(data.endSeconds!=undefined){
491
+ this.endSeconds = Number(data.endSeconds);
492
+ }
493
+ this.setAttribute("videoid",data.videoId);//いらないけど勘違い防止用に
494
+ this.setAttribute("service",data.service);
495
+ if(this.service && Object.keys((window as any).multi_embed_player.mep_load_api_promise).includes(this.service)){
496
+ await this.iframe_api_loader(this.service);
497
+ if(service_changed==false){
498
+ //動画idを変えてiframeを再読み込み
499
+ if(autoplay){
500
+ this.player.loadVideoById(data);
501
+ }
502
+ else{
503
+ this.player.cueVideoById(data);
504
+ }
505
+ }
506
+ else{
507
+ this.innerHTML = "";
508
+ let divdoc = document.createElement("div");
509
+ divdoc.classList.add(`mep_${this.service}`);
510
+ this.appendChild(divdoc);
511
+ let playerVars = {};
512
+ if(autoplay){
513
+ (playerVars as any).autoplay = 1;
514
+ }
515
+ if(data["startSeconds"]!=undefined){
516
+ (playerVars as any).startSeconds = data["startSeconds"];
517
+ }
518
+ if(data["endSeconds"]!=undefined){
519
+ (playerVars as any).endSeconds = data["endSeconds"];
520
+ }
521
+ let player_argument: Record<string, any> = {
522
+ "videoId":this.videoid,
523
+ "width":"560",
524
+ "height":"315",
525
+ "playerVars":playerVars
526
+ };
527
+ if(this.service=="bilibili"&&this.getAttribute("play_control_wrap")==="false"){
528
+ player_argument["play_control_wrap"] = false;
529
+ }
530
+ else{
531
+ player_argument["play_control_wrap"] = true;
532
+ }
533
+ if (this.service) {
534
+ this.player = new (window as any).multi_embed_player.iframe_api_class[this.service](divdoc,player_argument,this.#setEvent.bind(this));
535
+ this.player.service = this.service;
536
+ }
537
+ }
538
+ }
539
+ else{
540
+ console.error(`service name not defined ${this.service}`)
541
+ }
542
+ }
543
+ else{
544
+ console.log("player not found.");
545
+ }
546
+ }
547
+
548
+ #error_event_handler(e: any): void {
549
+ console.error("error occured");
550
+ if(!this.error_not_declare){
551
+ this.dispatchEvent(new CustomEvent("onError",{detail:{code:e.detail.code}}));
552
+ }
553
+ else{
554
+ this.dispatchEvent(new Event("executeSecound"));
555
+ }
556
+ }
557
+
558
+ /**
559
+ * Sets event listeners for the player.
560
+ * @param {HTMLElement} element - The element to set the event listeners on.
561
+ */
562
+ #setEvent(element?: HTMLElement): void {
563
+ try{
564
+ if(typeof element === "undefined"){
565
+ if(this.service && Object.keys((window as any).multi_embed_player.mep_load_api_promise).includes(this.service)){
566
+ this.player.player.addEventListener("onReady",()=>{this.dispatchEvent(new Event("onReady"))});//need bind
567
+ this.player.player.addEventListener("onError",(e: Event)=>{this.#error_event_handler(e)});
568
+ this.player.player.addEventListener("onStateChange",(e: CustomEvent)=>{this.dispatchEvent(new CustomEvent("onStateChange",{detail:e.detail}));});
569
+ this.player.player.addEventListener("onEndVideo",()=>{this.dispatchEvent(new Event("onEndVideo"))});
570
+ }
571
+ else{
572
+ console.error(`service ${this.service} not found at multi_embed_player class setEvent()`);
573
+ }
574
+ }
575
+ else{
576
+ if(this.service && Object.keys((window as any).multi_embed_player.mep_load_api_promise).includes(this.service)){
577
+ element.addEventListener("onReady",()=>{this.dispatchEvent(new Event("onReady"))});
578
+ element.addEventListener("onError",(e)=>{this.#error_event_handler(e)});
579
+ element.addEventListener("onStateChange",(e: Event)=>{this.dispatchEvent(new CustomEvent("onStateChange",{detail:(e as CustomEvent).detail}))});
580
+ element.addEventListener("onEndVideo",()=>{this.dispatchEvent(new Event("onEndVideo"))});
581
+ }
582
+ else{
583
+ console.error(`service ${this.service} not found at multi_embed_player class setEvent(element)`);
584
+ }
585
+ }
586
+ }
587
+ catch(e){
588
+ console.log("failed to set event. under log is error message.");
589
+ console.log(e);
590
+ }
591
+ }
592
+ /**
593
+ * Removes event listeners for the current player service.
594
+ * @returns {void}
595
+ */
596
+ #deleteEvent(): void {//plese before change service
597
+ try{
598
+ if(this.service && Object.keys((window as any).multi_embed_player.mep_load_api_promise).includes(this.service)){
599
+ this.player.player.removeEventListener("onReady",()=>{this.dispatchEvent(new Event("onReady"))});//need bind
600
+ this.player.player.removeEventListener("onError",(e: Event)=>{this.#error_event_handler(e)});
601
+ this.player.player.removeEventListener("onStateChange",(e: CustomEvent)=>{this.dispatchEvent(new CustomEvent("onStateChange",{detail:e.detail}));});
602
+ this.player.player.removeEventListener("onEndVideo",()=>{this.dispatchEvent(new Event("onEndVideo"))});
603
+ }
604
+ else{
605
+ console.error(`service ${this.service} not found at multi_embed_player class deleteEvent()`);
606
+ }
607
+ }
608
+ catch{}
609
+ }
610
+ /**
611
+ * Plays the video.
612
+ */
613
+ playVideo(): void {
614
+ this.player.playVideo();
615
+ }
616
+ /**
617
+ * Pauses the video.
618
+ */
619
+ pauseVideo(): void {
620
+ this.player.pauseVideo();
621
+ }
622
+ /**
623
+ * Stops the video.
624
+ * @deprecated
625
+ */
626
+ stopVideo(): void {//depricated
627
+ this.player.pauseVideo();
628
+ }
629
+ /**
630
+ * Returns the current time of the video.
631
+ * @returns {Promise<number>} - A promise that resolves with the current time of the video. promise only bilibili
632
+ */
633
+ async getCurrentTime(): Promise<number> {
634
+ if(this.service=="bilibili"){
635
+ return await this.player.getCurrentTime();
636
+ }
637
+ else{
638
+ return this.player.getCurrentTime();
639
+ }
640
+ }
641
+ /**
642
+ * Seeks to a given time in the video.
643
+ * @param {number} seconds - if service is bilibili, return promise
644
+ */
645
+ async seekTo(seconds: number): Promise<void> {
646
+ await this.player.seekTo(seconds);
647
+ }
648
+ /**
649
+ * Mutes the video.
650
+ */
651
+ mute(): void {
652
+ this.player.mute();
653
+ }
654
+ /**
655
+ * Unmutes the video.
656
+ */
657
+ unMute(): void {
658
+ this.player.unMute();
659
+ }
660
+ /**
661
+ * Returns whether the video is muted.
662
+ * @returns {boolean} - Whether the video is muted.if service is bilibili, return promise
663
+ */
664
+ isMuted(): boolean | Promise<boolean> {
665
+ return this.player.isMuted();
666
+ }
667
+ /**
668
+ * Set the volume of the player.
669
+ * @param {number} volume - The volume level to set.
670
+ */
671
+ setVolume(volume: number): void {
672
+ this.player.setVolume(Number(volume));
673
+ }
674
+ /**
675
+ * Returns the current volume of the player.
676
+ * @returns {number} The current volume of the player.
677
+ */
678
+ getVolume(): number {
679
+ return this.player.getVolume();
680
+ }
681
+ /**
682
+ * Returns the duration of the current video.
683
+ * @returns {number} The duration of the current video in seconds.
684
+ */
685
+ getDuration(): number {
686
+ return this.player.getDuration();
687
+ }
688
+ /**
689
+ * Returns the real duration of the video based on the start and end seconds.
690
+ * @returns {number} The real duration of the video.
691
+ */
692
+ getRealDulation(): number {
693
+ if(this.service && Object.keys((window as any).multi_embed_player.mep_load_api_promise).includes(this.service)){
694
+ return this.player.getRealDulation();
695
+ }
696
+ else{
697
+ return 0;
698
+ }
699
+ }
700
+ /**
701
+ * Returns the relative current time by subtracting the start time from the current time.
702
+ * @returns {Promise<number>} The relative current time.
703
+ */
704
+ async getRelativeCurrentTime(): Promise<number> {
705
+ return await this.getCurrentTime() - this.startSeconds;
706
+ }
707
+ /**
708
+ * Calculates the percentage of the current time relative to the total duration of the media.
709
+ * @returns {number} The percentage of the current time.
710
+ */
711
+ async getPercentOfCurremtTime(): Promise<number> {//notice sometimes over 100%
712
+ return ((await this.getRelativeCurrentTime())/this.getRealDulation())*100
713
+ }
714
+ /**
715
+ * Seeks to a relative position in the video based on the current time.
716
+ * @param {number} seconds - The number of seconds to seek relative to the current time.
717
+ */
718
+ async relativeSeekTo_ct(seconds: number): Promise<void> {//current time + seek time
719
+ this.seekTo(seconds + await this.getCurrentTime());
720
+ }
721
+ /**
722
+ * Start seeking from the given seconds plus the startSeconds.
723
+ * @param {number} seconds - The seconds to seek from.
724
+ */
725
+ relativeSeekTo_ss(seconds: number): void {//startSeconds + seek time
726
+ this.seekTo(seconds + this.startSeconds);
727
+ }
728
+ /**
729
+ * Returns the current state of the player.
730
+ * @returns {number} The player state:
731
+ * -1 -> not set video mainly before embed
732
+ * 0 -> not played only thumnail
733
+ * 1 -> onload
734
+ * 2 -> playing
735
+ * 3 -> pause
736
+ * 4 -> video ended
737
+ */
738
+ getPlayerState(): number {
739
+ if(this.service && Object.keys((window as any).multi_embed_player.mep_load_api_promise).includes(this.service)){
740
+ return this.player.getPlayerState();
741
+ }
742
+ else{
743
+ return -1
744
+ }
745
+ }
746
+ /**
747
+ * Plays a video on the specified player with the given parameters.
748
+ * @param {string} playerid - The ID of the player element.
749
+ * @param {string} service - The service provider (e.g. YouTube, Vimeo).
750
+ * @param {string} videoid - The ID of the video to play.
751
+ * @param {number|null} start - The start time of the video in seconds (optional).
752
+ * @param {number|null} end - The end time of the video in seconds (optional).
753
+ * @param {string|null} subService - The service provider for the subtitle (optional).
754
+ * @param {string|null} subVideoid - The ID of the subtitle video (optional).
755
+ */
756
+ #PlayOnPlayer(playerid: string, service: string, videoid: string, start: number | string | null, end: number | string | null, subService: string | null, subVideoid: string | null): void {
757
+ let playdoc = document.getElementById(playerid);
758
+ let content = new mep_playitem(service,videoid);
759
+ if(start!=null){
760
+ content.startSeconds = typeof start === 'string' ? Number(start) : start;
761
+ }
762
+ if(end!=null){
763
+ content.endSeconds = typeof end === 'string' ? Number(end) : end;
764
+ }
765
+ if(subService!=null&&subVideoid!=null){
766
+ content.subVideoid = subVideoid;
767
+ content.subService = subService as ServiceType;
768
+ }
769
+ (playdoc as any).loadVideoById(content.toData());
770
+ }
771
+ /**
772
+ * Loads the YouTube API asynchronously and returns a Promise that resolves when the API is ready.
773
+ * If the API is already loaded, the Promise resolves immediately.
774
+ * If the API is currently being loaded, the Promise will resolve when the API is ready.
775
+ * @returns {Promise<void>} A Promise that resolves when the YouTube API is ready.
776
+ */
777
+ async youtube_api_loader(): Promise<void> {
778
+ return new Promise<void>(async(resolve,reject)=>{
779
+ if((window as any).multi_embed_player.mep_status_load_api.youtube===0){
780
+ let script_url = "https://www.youtube.com/iframe_api";
781
+ (window as any).multi_embed_player.mep_status_load_api.youtube = 1;
782
+ await this.mep_promise_script_loader(script_url);
783
+ YT.ready(()=>{(window as any).multi_embed_player.mep_load_api_promise.youtube.forEach((func: any)=>func());(window as any).multi_embed_player.mep_status_load_api.youtube = 2;resolve()});
784
+ }
785
+ else if((window as any).multi_embed_player.mep_status_load_api.youtube==1){
786
+ (window as any).multi_embed_player.mep_load_api_promise.youtube.push(resolve);
787
+ }
788
+ else{
789
+ resolve();
790
+ }
791
+ });
792
+ }
793
+ /**
794
+ * Loads the API for the specified service and returns a promise that resolves when the API is loaded.
795
+ * @async
796
+ * @param {string} service - The name of the service whose API needs to be loaded.
797
+ * @returns {Promise<void>} A promise that resolves when the API is loaded.
798
+ */
799
+ async iframe_api_loader(service: string): Promise<void> {
800
+ return new Promise<void>(async(resolve,reject)=>{
801
+ if((window as any).multi_embed_player.mep_status_load_api[service]===0){
802
+ (window as any).multi_embed_player.mep_status_load_api[service] = 1;
803
+ await this.mep_promise_script_loader(`${(window as any).multi_embed_player.script_origin}iframe_api/${service}.js`);
804
+ (window as any).multi_embed_player.mep_status_load_api[service] = 2;
805
+ switch(service){
806
+ case "youtube":
807
+ (window as any).multi_embed_player.iframe_api_class["youtube"] = mep_youtube;
808
+ break;
809
+ case "niconico":
810
+ (window as any).multi_embed_player.iframe_api_class["niconico"] = mep_niconico;
811
+ break;
812
+ case "bilibili":
813
+ (window as any).multi_embed_player.iframe_api_class["bilibili"] = mep_bilibili;
814
+ break;
815
+ case "soundcloud":
816
+ (window as any).multi_embed_player.iframe_api_class["soundcloud"] = mep_soundcloud;
817
+ break;
818
+ }
819
+ (window as any).multi_embed_player.mep_load_api_promise[service].forEach((func: any)=>func());
820
+ resolve();
821
+ }
822
+ else if((window as any).multi_embed_player.mep_status_load_api[service]===1){
823
+ (window as any).multi_embed_player.mep_load_api_promise[service].push(resolve);
824
+ }
825
+ else{
826
+ resolve();
827
+ }
828
+ })
829
+ }
830
+ /**
831
+ * Loads a script asynchronously and returns a promise that resolves when the script is loaded successfully or rejects when there is an error.
832
+ * @param {string} src - The URL of the script to be loaded.
833
+ * @returns {Promise<void>} - A promise that resolves when the script is loaded successfully or rejects when there is an error.
834
+ */
835
+ async mep_promise_script_loader(src: string): Promise<void> {
836
+ return new Promise<void>((resolve,reject)=>{
837
+ let script_document = document.createElement("script");
838
+ script_document.src = src;
839
+ script_document.async = true;
840
+ document.body.appendChild(script_document);
841
+ script_document.addEventListener("load",()=>{
842
+ resolve();
843
+ },{once:true});
844
+ script_document.addEventListener("error",()=>{
845
+ reject();
846
+ },{once:true});
847
+ })
848
+ }
849
+ /**
850
+ * Adds a new play item to the playlist of the player.
851
+ * @function
852
+ * @name addPlaylist
853
+ * @memberof Player
854
+ * @returns {void}
855
+ */
856
+ #addPlaylist(): void {
857
+ let k_data = new mep_playitem(this.getAttribute("service"),this.getAttribute("videoid"));
858
+ if(this.getAttribute("start")!=null){
859
+ k_data.startSeconds = Number(this.getAttribute("start"));
860
+ }
861
+ if(this.getAttribute("end")!=null){
862
+ k_data.endSeconds = Number(this.getAttribute("end"));
863
+ }
864
+ if(this.getAttribute("subService")!=null&&this.getAttribute("subVideoid")!=null){
865
+ k_data.subService = this.getAttribute("subService") as ServiceType;
866
+ k_data.subVideoid = this.getAttribute("subVideoid") || undefined;
867
+ }
868
+ const forElement = this.getAttribute("for");
869
+ if (forElement) {
870
+ const targetElement = document.getElementById(forElement) as any;
871
+ if (targetElement) {
872
+ targetElement.playlist.push(k_data.toData());
873
+ targetElement.dispatchEvent(new Event("addPlaylist"));
874
+ }
875
+ }
876
+ }
877
+ }
878
+ class mep_playitem{
879
+ service: ServiceType;
880
+ videoid: string;
881
+ call_array: PlaylistItem[];
882
+ startSeconds: number | undefined;
883
+ endSeconds: number | undefined;
884
+ subService: ServiceType | undefined;
885
+ subVideoid: string | undefined;
886
+
887
+ constructor(service: any, videoid: any){
888
+ this.service = service;
889
+ this.videoid = videoid;
890
+ this.call_array = [];
891
+ }
892
+ toData(): PlaylistItem{
893
+ let content: PlaylistItem = {"service":this?.service,"videoId":this?.videoid,call_array:this.call_array,call_index:0};
894
+ if(this.service!==undefined&&this.videoid!==undefined){
895
+ content.call_array.push({videoId:this.videoid,service:this.service});
896
+ }
897
+ if(this.startSeconds!=undefined){
898
+ content.startSeconds = this.startSeconds;
899
+ }
900
+ if(this.endSeconds!=undefined){
901
+ content.endSeconds = this.endSeconds;
902
+ }
903
+ if(this.subService!=undefined&&this.subVideoid!=undefined){
904
+ content.subService = this.subService;
905
+ content.subVideoId = this.subVideoid;
906
+ content.call_array.push({videoId:this.subVideoid,service:this.subService});
907
+ }
908
+ return content
909
+ }
910
+ }
911
+ class mep_parallel{
912
+ data: mep_parallel_inner[];
913
+
914
+ constructor(){
915
+ this.data = [];//class mep_parallel_inner
916
+ }
917
+ parse(){
918
+
919
+ }
920
+ }
921
+ class mep_parallel_inner{
922
+ service: ServiceType;
923
+ videoid: string;
924
+
925
+ constructor(service: any, videoid: any){
926
+ this.service = service;
927
+ this.videoid = videoid;
928
+ }
929
+ }
930
+ if(typeof multi_embed_player_set_variable === "function"){
931
+ multi_embed_player_set_variable(multi_embed_player);
932
+ }
933
+
934
+ //load GDPR status
935
+ try{
936
+ if(localStorage.getItem("multi_embed_player_GDPR_accepted")!==null){
937
+ const gdprData = localStorage.getItem("multi_embed_player_GDPR_accepted");
938
+ if (gdprData) {
939
+ (window as any).multi_embed_player.GDPR_accepted = JSON.parse(gdprData);
940
+ }
941
+ }
942
+ }
943
+ catch{
944
+ console.log("failed to load GDPR status may be not supported browser or not accept to access localstorage");
945
+ }
946
+
947
+ const multi_embed_player_save_GDPR_status = ()=>{
948
+ try{
949
+ localStorage.setItem("multi_embed_player_GDPR_accepted",JSON.stringify(multi_embed_player.GDPR_accepted));
950
+ }
951
+ catch{
952
+ console.log("failed to save GDPR status may be not supported browser or not accept to access localstorage");
953
+ }
954
+ }
955
+
956
+ //recieve GDPR accept by service
957
+ const multi_embed_player_GDPR_reviever = (service: ServiceType): void =>{
958
+ multi_embed_player.GDPR_accepted[service] = true;
959
+ multi_embed_player_save_GDPR_status();
960
+ multi_embed_player.GDPR_accept_promise[service].forEach((func: () => void)=>func());
961
+ }
962
+
963
+ //Add multi embed player CSS
964
+ const multi_embed_player_css = document.createElement("style");
965
+ multi_embed_player_css.innerHTML = `
966
+ multi-embed-player{
967
+ display: block;
968
+ position: relative;
969
+ background-repeat: no-repeat;
970
+ background-position: center center;
971
+ background-size: cover;
972
+ }
973
+ multi-embed-player>iframe{
974
+ width: 100%;
975
+ height: 100%;
976
+ }
977
+ multi-embed-player>picture{
978
+ position: absolute;
979
+ width: 100%;
980
+ height: 100%;
981
+ z-index: -1;
982
+ }`;
983
+ multi_embed_player_css.classList.add("multi-embed-player-CSS");
984
+ document.head.appendChild(multi_embed_player_css);
985
+
986
+ // Preserve class name for compilation
987
+ (window as any).multi_embed_player = multi_embed_player;
988
+
989
+ customElements.define('multi-embed-player', multi_embed_player);//define custom element