From 61c5775869c0aba0fc52e73adf5174d929924026 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 4 May 2023 20:33:13 +0000 Subject: [PATCH] Release: version 0.7.1 --- Dockerfile | 2 +- Readme.md | 2 +- client/dist/assets/css/{app.de64fc4c.css => app.114f8e66.css} | 2 +- client/dist/assets/js/app.238feba1.js | 2 ++ client/dist/assets/js/app.238feba1.js.map | 1 + client/dist/assets/js/app.8188c78a.js | 2 -- client/dist/assets/js/app.8188c78a.js.map | 1 - client/dist/index.html | 2 +- client/dist/service-worker.js | 2 +- client/dist/service-worker.js.map | 2 +- client/package.json | 2 +- installer/KonomiTV-Installer.py | 2 +- server/app/constants.py | 2 +- 13 files changed, 12 insertions(+), 12 deletions(-) rename client/dist/assets/css/{app.de64fc4c.css => app.114f8e66.css} (94%) create mode 100644 client/dist/assets/js/app.238feba1.js create mode 100644 client/dist/assets/js/app.238feba1.js.map delete mode 100644 client/dist/assets/js/app.8188c78a.js delete mode 100644 client/dist/assets/js/app.8188c78a.js.map diff --git a/Dockerfile b/Dockerfile index 79435f8d..661b9c0d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends aria2 ca-certif # サードパーティーライブラリをダウンロード ## サードパーティーライブラリは変更が少ないので、先にダウンロード処理を実行してビルドキャッシュを効かせる WORKDIR / -RUN aria2c -x10 https://github.com/tsukumijima/KonomiTV/releases/download/v0.7.0/thirdparty-linux.tar.xz +RUN aria2c -x10 https://github.com/tsukumijima/KonomiTV/releases/download/v0.7.1/thirdparty-linux.tar.xz RUN tar xvf thirdparty-linux.tar.xz # -------------------------------------------------------------------------------------------------------------- diff --git a/Readme.md b/Readme.md index 7bbdf9b6..c1ae9373 100644 --- a/Readme.md +++ b/Readme.md @@ -379,7 +379,7 @@ Docker Compose は V1 と V2 の両方に対応していますが、できれば
```bash -curl -LO https://github.com/tsukumijima/KonomiTV/releases/download/v0.7.0/KonomiTV-Installer.elf +curl -LO https://github.com/tsukumijima/KonomiTV/releases/download/v0.7.1/KonomiTV-Installer.elf chmod a+x KonomiTV-Installer.elf ./KonomiTV-Installer.elf ``` diff --git a/client/dist/assets/css/app.de64fc4c.css b/client/dist/assets/css/app.114f8e66.css similarity index 94% rename from client/dist/assets/css/app.de64fc4c.css rename to client/dist/assets/css/app.114f8e66.css index ea60afaa..f1003031 100644 --- a/client/dist/assets/css/app.de64fc4c.css +++ b/client/dist/assets/css/app.114f8e66.css @@ -1 +1 @@ -@font-face{font-family:Noto Sans JP Caption;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/noto-sans-japanese@1.0.0/fonts/NotoSansJP-Medium.woff2) format("woff2")}@font-face{font-family:"Open Sans (for iOS Safari)";font-style:normal;font-weight:400;font-stretch:100%;src:url(https://fonts.gstatic.com/s/opensans/v34/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4uaVIGxA.woff2) format("woff2");unicode-range:u+0100-024f,u+0259,u+1e??,u+2020,u+20a0-20ab,u+20ad-20cf,u+2113,u+2c60-2c7f,u+a720-a7ff}@font-face{font-family:"Open Sans (for iOS Safari)";font-style:normal;font-weight:400;font-stretch:100%;src:url(https://fonts.gstatic.com/s/opensans/v34/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVI.woff2) format("woff2");unicode-range:u+00??,u+0131,u+0152-0153,u+02bb-02bc,u+02c6,u+02da,u+02dc,u+2000-206f,u+2074,u+20ac,u+2122,u+2191,u+2193,u+2212,u+2215,u+feff,u+fffd}@font-face{font-family:"Open Sans (for iOS Safari)";font-style:normal;font-weight:600;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/opensans/v34/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format("woff2");unicode-range:u+0100-024f,u+0259,u+1e??,u+2020,u+20a0-20ab,u+20ad-20cf,u+2113,u+2c60-2c7f,u+a720-a7ff}@font-face{font-family:"Open Sans (for iOS Safari)";font-style:normal;font-weight:600;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/opensans/v34/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format("woff2");unicode-range:u+00??,u+0131,u+0152-0153,u+02bb-02bc,u+02c6,u+02da,u+02dc,u+2000-206f,u+2074,u+20ac,u+2122,u+2191,u+2193,u+2212,u+2215,u+feff,u+fffd}@font-face{font-family:"Open Sans (for iOS Safari)";font-style:normal;font-weight:700;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/opensans/v34/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format("woff2");unicode-range:u+0100-024f,u+0259,u+1e??,u+2020,u+20a0-20ab,u+20ad-20cf,u+2113,u+2c60-2c7f,u+a720-a7ff}@font-face{font-family:"Open Sans (for iOS Safari)";font-style:normal;font-weight:700;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/opensans/v34/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format("woff2");unicode-range:u+00??,u+0131,u+0152-0153,u+02bb-02bc,u+02c6,u+02da,u+02dc,u+2000-206f,u+2074,u+20ac,u+2122,u+2191,u+2193,u+2212,u+2215,u+feff,u+fffd}html{overflow-y:auto!important;touch-action:manipulation}body .v-application{min-height:100vh;min-height:100dvh;font-family:YakuHanJPs,Open Sans,Hiragino Sans,Noto Sans JP,sans-serif;font-weight:500;overflow-x:clip;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@supports(-webkit-touch-callout:none){body .v-application{min-height:-webkit-fill-available;font-family:YakuHanJPs,"Open Sans (for iOS Safari)",Hiragino Sans,Noto Sans JP,sans-serif;font-weight:400}}body .v-application .v-application--wrap{min-height:100%!important}body main{display:flex;width:100%;min-height:100%}body header+main{padding-top:65px!important}@media(max-width:1000px)and (max-height:450px){body header+main{padding-top:0!important}}@media(max-width:600px)and (min-height:450.01px){body header+main{padding-top:0!important;padding-bottom:calc(env(safe-area-inset-bottom) + 56px)!important}}body .route-container{height:100%;background:var(--v-background-base)}.v-btn{letter-spacing:0!important;text-transform:none!important}@media(max-width:600px)and (min-height:450.01px){.v-snack{padding-bottom:56px!important}}.v-snack .v-btn__content{color:var(--v-primary-lighten1);letter-spacing:.3}.v-snack .error .v-btn__content,.v-snack .info .v-btn__content,.v-snack .success .v-btn__content,.v-snack .warning .v-btn__content{color:var(--v-text-base);letter-spacing:.3}.v-snack:not(.v-snack--absolute){height:100dvh!important}@media(max-width:600px)and (min-height:450.01px){.v-dialog{margin:24px 6px!important}.v-dialog--fullscreen{margin:0!important}}.v-popper--theme-tooltip .v-popper__inner{display:inline-block;padding:4px 10px;border-radius:4px;background:var(--v-background-lighten1);color:var(--v-text-base);font-size:12px;font-family:YakuHanJPs,Open Sans,Hiragino Sans,Noto Sans JP,sans-serif;font-weight:500;opacity:.9;line-height:22px}.v-popper--theme-tooltip .v-popper__arrow-container{display:none}@media(hover:none){:hover:before{background-color:transparent!important}}::-moz-selection{background-color:#e64f9780}::selection{background-color:#e64f9780}.decorate-symbol{display:inline-flex;justify-content:center;align-items:center;position:relative;padding:0 3px;margin-left:2.5px;margin-right:2.5px;border-radius:4px;color:var(--v-text-base);background:var(--v-primary-base);font-size:.94em}*{scrollbar-color:var(--v-gray-base) var(--v-background-base);scrollbar-width:thin}::-webkit-scrollbar{width:7px;height:7px}::-webkit-scrollbar-track{background:var(--v-background-base)}::-webkit-scrollbar-thumb{background:var(--v-background-lighten2)}::-webkit-scrollbar-thumb:hover{background:var(--v-gray-base)}@media(max-width:600px)and (min-height:450.01px){.v-menu__content .v-list{background-color:var(--v-background-lighten1)!important}}.v-menu__content::-webkit-scrollbar{width:12px;height:12px}.v-menu__content::-webkit-scrollbar-thumb{border:solid 3.5px var(--v-background-base)}.v-card__text{font-weight:inherit!important}.v-enter-active,.v-leave-active{transition:opacity .25s}.v-enter,.v-leave-to{opacity:0}.v-enter-active.route-container{position:fixed;top:0;left:0;right:0}.cursor-pointer{cursor:pointer}.w-25{width:25%}.w-50{width:50%}.w-75{width:75%}.w-100{width:100%}.h-25{height:25%}.h-50{height:50%}.h-75{height:75%}.h-100{height:100%}.header[data-v-84897154]{position:fixed;display:flex;align-items:center;width:100%;height:65px;padding:4px 16px;background:var(--v-background-base);box-shadow:0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12);z-index:10}@media(max-width:1000px)and (max-height:450px){.header[data-v-84897154]{width:210px;height:48px;justify-content:center}}@media(max-width:680px)and (max-height:450px){.header[data-v-84897154]{width:190px}}@media(max-width:600px)and (min-height:450.01px){.header[data-v-84897154]{display:none}}@media(max-width:1000px)and (max-height:450px){.header .spacer[data-v-84897154]{display:none}}.header .konomitv-logo[data-v-84897154]{display:block;padding:12px 8px;border-radius:8px}@media(max-width:1000px)and (max-height:450px){.header .konomitv-logo[data-v-84897154]{margin:0!important}}.header .konomitv-logo__image[data-v-84897154]{display:block}@media(max-width:1000px)and (max-height:450px){.header .konomitv-logo__image[data-v-84897154]{height:19.5px}}.bottom-navigation-container[data-v-3df53df3]{display:none;position:fixed;bottom:0;z-index:8}@media(max-width:600px)and (min-height:450.01px){.bottom-navigation-container[data-v-3df53df3]{display:flex;padding-bottom:env(safe-area-inset-bottom);box-sizing:content-box}}.bottom-navigation-container .v-btn.bottom-navigation-button[data-v-3df53df3]{color:var(--v-text-darken1)!important;font-weight:700}.bottom-navigation-container .v-btn.bottom-navigation-button.v-btn--active[data-v-3df53df3]{color:var(--v-primary-base)!important}.navigation-container[data-v-5b40940b]{flex-shrink:0;width:220px;background:var(--v-background-lighten1)}@media(max-width:1000px)and (max-height:450px){.navigation-container[data-v-5b40940b]{width:210px}}@media(max-width:680px)and (max-height:450px){.navigation-container[data-v-5b40940b]{width:190px}}@media(max-width:600px)and (min-height:450.01px){.navigation-container[data-v-5b40940b]{display:none}}.navigation-container .navigation[data-v-5b40940b]{position:fixed;width:220px;top:65px;left:0;bottom:-100px;padding-bottom:100px;background:var(--v-background-lighten1);z-index:1}@media(max-width:1000px)and (max-height:450px){.navigation-container .navigation[data-v-5b40940b]{top:48px;width:210px}}@media(max-width:680px)and (max-height:450px){.navigation-container .navigation[data-v-5b40940b]{width:190px}}.navigation-container .navigation .navigation-scroll[data-v-5b40940b]{display:flex;flex-direction:column;height:100%;padding:22px 12px;overflow-y:auto}@media(max-width:1000px)and (max-height:450px){.navigation-container .navigation .navigation-scroll[data-v-5b40940b]{padding:10px 12px}}@media(max-width:680px)and (max-height:450px){.navigation-container .navigation .navigation-scroll[data-v-5b40940b]{padding:10px 8px}}.navigation-container .navigation .navigation-scroll[data-v-5b40940b]::-webkit-scrollbar-track{background:var(--v-background-lighten1)}.navigation-container .navigation .navigation-scroll .navigation__link[data-v-5b40940b]{display:flex;align-items:center;flex-shrink:0;height:52px;padding-left:16px;margin-top:4px;border-radius:11px;font-size:16px;color:var(--v-text-base);transition:background-color .15s;text-decoration:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(max-width:1000px)and (max-height:450px){.navigation-container .navigation .navigation-scroll .navigation__link[data-v-5b40940b]{height:40px;padding-left:12px;border-radius:9px;font-size:15px}}.navigation-container .navigation .navigation-scroll .navigation__link[data-v-5b40940b]:hover{background:var(--v-background-lighten2)}.navigation-container .navigation .navigation-scroll .navigation__link[data-v-5b40940b]:first-of-type{margin-top:0}.navigation-container .navigation .navigation-scroll .navigation__link--active[data-v-5b40940b]{color:var(--v-primary-base);background:#5b2d3c}.navigation-container .navigation .navigation-scroll .navigation__link--active[data-v-5b40940b]:hover{background:#5b2d3c}.navigation-container .navigation .navigation-scroll .navigation__link--highlight[data-v-5b40940b]{color:var(--v-secondary-lighten1)}.navigation-container .navigation .navigation-scroll .navigation__link--version[data-v-5b40940b]{font-size:15px}@media(max-width:1000px)and (max-height:450px){.navigation-container .navigation .navigation-scroll .navigation__link--version[data-v-5b40940b]{font-size:14.5px}}.navigation-container .navigation .navigation-scroll .navigation__link .navigation__link-icon[data-v-5b40940b]{margin-right:14px}@media(max-width:1000px)and (max-height:450px){.navigation-container .navigation .navigation-scroll .navigation__link .navigation__link-icon[data-v-5b40940b]{margin-right:10px}.login-container-wrapper[data-v-851c3dec]{padding:20px!important;margin-bottom:0!important}}@media(max-width:600px)and (min-height:450.01px){.login-container-wrapper[data-v-851c3dec]{margin-bottom:0!important}}.login-container-wrapper .login-container[data-v-851c3dec]{border-radius:11px}@media(max-width:1000px)and (max-height:450px){.login-container-wrapper .login-container[data-v-851c3dec]{padding:24px!important}}@media(max-width:600px)and (min-height:450.01px){.login-container-wrapper .login-container[data-v-851c3dec]{padding:32px 20px!important;margin-left:12px!important;margin-right:12px!important}}@media(max-width:1000px)and (max-height:450px){.login-container-wrapper .login-container .login__logo[data-v-851c3dec]{padding-top:4px!important;padding-bottom:8px!important}.login-container-wrapper .login-container .login__logo .v-image[data-v-851c3dec]{max-width:200px!important}.login-container-wrapper .login-container .login__logo h4[data-v-851c3dec]{margin-top:16px!important;font-size:19px!important}}@media(max-width:600px)and (min-height:450.01px){.login-container-wrapper .login-container .login__logo[data-v-851c3dec]{padding-top:4px!important;padding-bottom:12px!important}.login-container-wrapper .login-container .login__logo .v-image[data-v-851c3dec]{max-width:200px!important}.login-container-wrapper .login-container .login__logo h4[data-v-851c3dec]{margin-top:24px!important;font-size:19px!important}}@media(max-width:1000px)and (max-height:450px){.login-container-wrapper .login-container .v-input[data-v-851c3dec]{margin-top:24px!important;font-size:14px!important}}@media(max-width:600px)and (min-height:450.01px){.login-container-wrapper .login-container .v-input[data-v-851c3dec]{margin-top:32px!important;font-size:16px!important}}.login-container-wrapper .login-container .login-button[data-v-851c3dec]{border-radius:7px;margin-top:48px!important;font-size:18px;letter-spacing:0}@media(max-width:1000px)and (max-height:450px){.login-container-wrapper .login-container .login-button[data-v-851c3dec]{height:44px!important;margin-top:24px!important;font-size:16px}}@media(max-width:600px)and (min-height:450.01px){.login-container-wrapper .login-container .login-button[data-v-851c3dec]{height:50px!important;margin-top:32px!important;font-size:15.5px}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){h1[data-v-1310cfee]{font-size:24px!important}}@media(max-width:1000px)and (max-height:450px){h1[data-v-1310cfee]{font-size:24px!important}}@media(max-width:600px)and (min-height:450.01px){h1[data-v-1310cfee]{font-size:24px!important;text-align:center}}@media(max-width:1000px)and (max-height:450px){span[data-v-1310cfee]{font-size:15px!important}}@media(max-width:600px)and (min-height:450.01px){span[data-v-1310cfee]{font-size:14px!important}}@media(max-width:1000px)and (max-height:450px){.register-container-wrapper[data-v-6533f3d0]{padding:20px!important;margin-bottom:0!important}}@media(max-width:600px)and (min-height:450.01px){.register-container-wrapper[data-v-6533f3d0]{margin-bottom:0!important}}.register-container-wrapper .register-container[data-v-6533f3d0]{border-radius:11px}@media(max-width:1000px)and (max-height:450px){.register-container-wrapper .register-container[data-v-6533f3d0]{padding:24px!important}}@media(max-width:600px)and (min-height:450.01px){.register-container-wrapper .register-container[data-v-6533f3d0]{padding:32px 20px!important;margin-left:12px!important;margin-right:12px!important}}@media(max-width:1000px)and (max-height:450px){.register-container-wrapper .register-container .register__logo[data-v-6533f3d0]{padding-top:4px!important;padding-bottom:8px!important}.register-container-wrapper .register-container .register__logo .v-image[data-v-6533f3d0]{max-width:200px!important}.register-container-wrapper .register-container .register__logo h4[data-v-6533f3d0]{margin-top:16px!important;font-size:19px!important}}@media(max-width:600px)and (min-height:450.01px){.register-container-wrapper .register-container .register__logo[data-v-6533f3d0]{padding-top:4px!important;padding-bottom:12px!important}.register-container-wrapper .register-container .register__logo .v-image[data-v-6533f3d0]{max-width:200px!important}.register-container-wrapper .register-container .register__logo h4[data-v-6533f3d0]{margin-top:24px!important;font-size:19px!important}}@media(max-width:1000px)and (max-height:450px){.register-container-wrapper .register-container .v-input[data-v-6533f3d0]{margin-top:0!important;font-size:14px!important}}@media(max-width:600px)and (min-height:450.01px){.register-container-wrapper .register-container .v-input[data-v-6533f3d0]{margin-top:2px!important;font-size:16px!important}}@media(max-width:1000px)and (max-height:450px){.register-container-wrapper .register-container .v-input[data-v-6533f3d0]:first-child{margin-top:24px!important}}@media(max-width:600px)and (min-height:450.01px){.register-container-wrapper .register-container .v-input[data-v-6533f3d0]:first-child{margin-top:32px!important}}.register-container-wrapper .register-container .register-button[data-v-6533f3d0]{border-radius:7px;margin-top:18px!important;font-size:18px;letter-spacing:0}@media(max-width:1000px)and (max-height:450px){.register-container-wrapper .register-container .register-button[data-v-6533f3d0]{height:44px!important;margin-top:0!important;font-size:16px}}@media(max-width:600px)and (min-height:450.01px){.register-container-wrapper .register-container .register-button[data-v-6533f3d0]{height:50px!important;margin-top:2px!important;font-size:15.5px}}.settings-container[data-v-d0f5a998]{width:100%;min-width:0}@media(max-width:1000px)and (max-height:450px){.settings-container[data-v-d0f5a998]{padding:16px 20px!important}}@media(max-width:680px)and (max-height:450px){.settings-container[data-v-d0f5a998]{padding:16px 16px!important}}@media(max-width:600px)and (min-height:450.01px){.settings-container[data-v-d0f5a998]{padding:0 0!important}}.settings-container .settings-navigation[data-v-d0f5a998]{position:sticky;top:85px!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.settings-container .settings-navigation[data-v-d0f5a998]{display:none}}@media(max-width:1000px)and (max-height:450px){.settings-container .settings-navigation[data-v-d0f5a998]{display:none}}@media(max-width:600px)and (min-height:450.01px){.settings-container .settings-navigation[data-v-d0f5a998]{display:none}}.settings-container .settings-navigation .v-list-item--link[data-v-d0f5a998],.settings-container .settings-navigation .v-list-item--link[data-v-d0f5a998]:before{border-radius:11px!important}.settings-container .settings[data-v-d0f5a998]{width:100%;min-width:0;border-radius:11px!important;background-color:var(--v-background-lighten1)}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.settings-container .settings[data-v-d0f5a998]{margin-left:0!important;padding-top:20px!important;padding-left:20px!important;padding-right:20px!important}}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998]{padding:20px!important;margin-left:0!important}}@media(max-width:600px)and (min-height:450.01px){.settings-container .settings[data-v-d0f5a998]{margin-left:0!important;padding-top:60px!important;padding-left:16px!important;padding-right:16px!important;border-radius:0!important;background-color:var(--v-background-base)}}.settings-container .settings[data-v-d0f5a998] .settings__heading{display:flex;align-items:center;font-size:22px}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__heading{font-size:20px}}@media(max-width:600px)and (min-height:450.01px){.settings-container .settings[data-v-d0f5a998] .settings__heading{position:fixed;top:0;left:0;right:0;height:60px;padding:16px;border-radius:0;background:var(--v-background-lighten1);box-shadow:0 3px 14px 2px rgba(0,0,0,.12);z-index:5}}.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button{display:none;position:relative;left:-6px;padding:6px;border-radius:50%;color:var(--v-text-base)}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button{display:flex}}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button{display:flex}}@media(max-width:600px)and (min-height:450.01px){.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button{display:flex}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button+svg{display:none}}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button+svg{display:none}}@media(max-width:600px)and (min-height:450.01px){.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button+svg{display:none}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button+svg+span{margin-left:0!important}}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button+svg+span{margin-left:0!important}}@media(max-width:600px)and (min-height:450.01px){.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button+svg+span{margin-left:0!important}}.settings-container .settings[data-v-d0f5a998] .settings__content{margin-top:24px}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__content{margin-top:16px}}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item{display:flex;position:relative;flex-direction:column;margin-top:24px}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item{margin-top:16px}}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item--sync-disabled .settings__item-heading{padding-right:8px}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item--sync-disabled .settings__item-heading:after{content:"デバイス間同期無効";display:flex;align-items:center;flex-shrink:0;position:relative;right:-8px;padding:2px 4px;margin-left:auto;border-radius:4px;background:var(--v-background-lighten2);font-size:11px}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item--switch{margin-right:62px}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item--switch .settings__item-heading{width:calc(100% + 62px)}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item--disabled{opacity:.5}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item-heading{display:flex;align-items:center;color:var(--v-text-base);font-size:16.5px}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item-heading{font-size:15px}}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item-label{margin-top:8px;color:var(--v-text-darken1);font-size:13.5px;line-height:1.6}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item-label{font-size:11px;line-height:1.7}}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item-form{margin-top:14px}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item-form{font-size:13.5px}}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item-switch{align-items:center;position:absolute;top:0;right:-74px;bottom:0;margin-top:0}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item p{margin-bottom:8px}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item p:last-of-type{margin-bottom:0}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__save-button{height:45px;background:var(--v-background-lighten2);font-size:15.5px;letter-spacing:0}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__content .settings__save-button{height:40px;padding:0 12px;font-size:14px}}.settings__content[data-v-7749b102]{opacity:1;transition:opacity .4s}.settings__content--loading[data-v-7749b102]{opacity:0}@media(max-width:600px)and (min-height:450.01px){.settings__conflict-dialog .v-btn[data-v-7749b102]{height:50px!important;text-align:left}}.settings__conflict-dialog .v-btn br.smartphone-vertical-only[data-v-7749b102]{display:none}@media(max-width:600px)and (min-height:450.01px){.settings__conflict-dialog .v-btn br.smartphone-vertical-only[data-v-7749b102]{display:inline}}.account[data-v-7749b102]{display:flex;align-items:center;height:130px;padding:18px 20px;border-radius:15px;background:var(--v-background-lighten2)}@media(min-width:1000.01px)and (max-width:1264px)and (max-height:850px){.account[data-v-7749b102]{align-items:normal;flex-direction:column;height:auto;padding:16px}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.account[data-v-7749b102]{align-items:normal;flex-direction:column;height:auto;padding:16px}}@media(max-width:1000px)and (max-height:450px){.account[data-v-7749b102]{align-items:normal;flex-direction:column;height:auto;padding:16px;border-radius:10px}}@media(max-width:600px)and (min-height:450.01px){.account[data-v-7749b102]{align-items:normal;flex-direction:column;height:auto;padding:16px 12px;border-radius:10px}}.account-wrapper[data-v-7749b102]{display:flex;align-items:center;min-width:0;height:94px}@media(max-width:1000px)and (max-height:450px){.account-wrapper[data-v-7749b102]{height:80px}}@media(max-width:600px)and (min-height:450.01px){.account-wrapper[data-v-7749b102]{height:70px}}.account__icon[data-v-7749b102]{flex-shrink:0;min-width:94px;height:100%;border-radius:50%;-o-object-fit:cover;object-fit:cover;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2));image-rendering:-webkit-optimize-contrast}@media(max-width:1000px)and (max-height:450px){.account__icon[data-v-7749b102]{min-width:80px}}@media(max-width:600px)and (min-height:450.01px){.account__icon[data-v-7749b102]{min-width:70px}}.account__info[data-v-7749b102]{display:flex;flex-direction:column;min-width:0;margin-left:20px;margin-right:12px}@media(max-width:600px)and (min-height:450.01px){.account__info[data-v-7749b102]{margin-left:12px!important;margin-right:0!important}}.account__info-name[data-v-7749b102]{display:inline-flex;align-items:center;height:33px}.account__info-name-text[data-v-7749b102]{display:inline-block;font-size:23px;color:var(--v-text-base);font-weight:700;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}@media(max-width:1000px)and (max-height:450px){.account__info-name-text[data-v-7749b102]{font-size:21px}}@media(max-width:600px)and (min-height:450.01px){.account__info-name-text[data-v-7749b102]{font-size:20px}}.account__info-admin[data-v-7749b102]{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:52px;height:28px;margin-left:10px;border-radius:5px;background:var(--v-secondary-base);font-size:14px;font-weight:500;line-height:2}@media(max-width:1000px)and (max-height:450px){.account__info-admin[data-v-7749b102]{width:45px;height:24px;border-radius:4px;font-size:11.5px}}@media(max-width:600px)and (min-height:450.01px){.account__info-admin[data-v-7749b102]{width:45px;height:24px;border-radius:4px;font-size:11.5px}}.account__info-id[data-v-7749b102]{display:inline-block;margin-top:2px;color:var(--v-text-darken1);font-size:16px}@media(max-width:1000px)and (max-height:450px){.account__info-id[data-v-7749b102]{font-size:14.5px}}@media(max-width:600px)and (min-height:450.01px){.account__info-id[data-v-7749b102]{font-size:14.5px}}.account__login[data-v-7749b102]{border-radius:7px;font-size:16px;letter-spacing:0}@media(min-width:1000.01px)and (max-width:1264px)and (max-height:850px){.account__login[data-v-7749b102]{height:50px!important;margin-top:8px;margin-right:auto;font-size:14.5px}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.account__login[data-v-7749b102]{height:42px!important;margin-top:8px;margin-right:auto;font-size:14.5px}}@media(max-width:1000px)and (max-height:450px){.account__login[data-v-7749b102]{height:42px!important;margin-top:8px;margin-right:auto;font-size:14.5px}}@media(max-width:600px)and (min-height:450.01px){.account__login[data-v-7749b102]{height:42px!important;margin-top:16px;margin-right:auto;font-size:14.5px}}.account-register[data-v-7749b102]{display:flex;flex-direction:column;margin-top:28px}.account-register__heading[data-v-7749b102]{font-size:21px;font-weight:700;text-align:center;font-feature-settings:"palt" 1;letter-spacing:.04em}@media(max-width:1000px)and (max-height:450px){.account-register__heading[data-v-7749b102]{font-size:19px}}@media(max-width:600px)and (min-height:450.01px){.account-register__heading[data-v-7749b102]{font-size:18px}}.account-register__feature[data-v-7749b102]{display:grid;grid-template-columns:1fr 1fr;grid-row-gap:18px;grid-column-gap:16px;margin-top:28px}@media(min-width:1000.01px)and (max-width:1264px)and (max-height:850px){.account-register__feature[data-v-7749b102]{grid-template-columns:1fr}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.account-register__feature[data-v-7749b102]{grid-template-columns:1fr}}@media(max-width:1000px)and (max-height:450px){.account-register__feature[data-v-7749b102]{grid-template-columns:1fr}}@media(max-width:600px)and (min-height:450.01px){.account-register__feature[data-v-7749b102]{grid-template-columns:1fr}}.account-register__feature .account-feature[data-v-7749b102]{display:flex;align-items:center}.account-register__feature .account-feature__icon[data-v-7749b102]{width:46px;height:46px;flex-shrink:0;margin-right:16px;color:var(--v-secondary-lighten1)}.account-register__feature .account-feature__info[data-v-7749b102]{display:flex;flex-direction:column}.account-register__feature .account-feature__info-heading[data-v-7749b102]{font-size:15px}.account-register__feature .account-feature__info-text[data-v-7749b102]{margin-top:3px;color:var(--v-text-darken1);font-size:12.5px;line-height:1.65}.account-register__description[data-v-7749b102]{margin-top:32px;font-size:15px;line-height:1.7;text-align:center}.account-register__description br.smartphone-vertical-only[data-v-7749b102]{display:none}@media(min-width:1000.01px)and (max-width:1264px)and (max-height:850px){.account-register__description[data-v-7749b102]{font-size:12.5px}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.account-register__description[data-v-7749b102]{font-size:10.5px}}@media(max-width:1000px)and (max-height:450px){.account-register__description[data-v-7749b102]{font-size:12.5px}}@media(max-width:680px)and (max-height:450px){.account-register__description[data-v-7749b102]{font-size:10.5px}}@media(max-width:600px)and (min-height:450.01px){.account-register__description[data-v-7749b102]{font-size:12.5px}.account-register__description br.smartphone-vertical-only[data-v-7749b102]{display:inline}}.account-register__button[data-v-7749b102]{margin-top:24px;margin-left:auto;margin-right:auto;border-radius:7px;font-size:16px;letter-spacing:0}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.account-register__button[data-v-7749b102]{height:42px!important;font-size:14.5px}}@media(max-width:1000px)and (max-height:450px){.account-register__button[data-v-7749b102]{height:42px!important;font-size:14.5px}}@media(max-width:600px)and (min-height:450.01px){.account-register__button[data-v-7749b102]{max-width:210px!important;height:42px!important;font-size:15px}}.settings-container[data-v-48d089f3]{width:100%;min-width:0}@media(max-width:1000px)and (max-height:450px){.settings-container[data-v-48d089f3]{padding:16px 20px!important}}@media(max-width:680px)and (max-height:450px){.settings-container[data-v-48d089f3]{padding:16px 16px!important}}@media(max-width:600px)and (min-height:450.01px){.settings-container[data-v-48d089f3]{padding:16px 16px!important}}.settings-container .settings-navigation[data-v-48d089f3]{transform:none!important;visibility:visible!important}.settings-container .settings-navigation .settings-navigation-version[data-v-48d089f3]{display:none}@media(max-width:600px)and (min-height:450.01px){.settings-container .settings-navigation .settings-navigation-version[data-v-48d089f3]{display:flex}}.settings-container .settings-navigation .settings-navigation-version--highlight[data-v-48d089f3]{color:var(--v-secondary-lighten1)}@media(max-width:1000px)and (max-height:450px){.settings-container .settings-navigation h1[data-v-48d089f3]{font-size:22px!important}.settings-container .settings-navigation .v-navigation-drawer__content .v-list[data-v-48d089f3]{margin-top:12px!important;padding:0!important}}.settings-container .settings-navigation .v-list-item--link[data-v-48d089f3],.settings-container .settings-navigation .v-list-item--link[data-v-48d089f3]:before{background:var(--v-background-lighten1);border-radius:6px!important;margin-top:6px!important;margin-bottom:0!important}.settings-container .settings-navigation .v-list-item--link[data-v-48d089f3]:first-of-type{margin-top:0!important}.settings-container .settings-navigation .v-list-item__icon[data-v-48d089f3]{margin:14px 0!important;margin-right:16px!important}@media(max-width:600px)and (min-height:450.01px){.comment-mute-settings .v-card__title[data-v-2cd59ba0],.comment-mute-settings>div[data-v-2cd59ba0]{padding-left:12px!important;padding-right:12px!important}}.settings__item[data-v-2cd59ba0]{display:flex;position:relative;flex-direction:column;margin-top:24px}@media(max-width:1000px)and (max-height:450px){.settings__item[data-v-2cd59ba0]{margin-top:16px}}.settings__item--switch[data-v-2cd59ba0]{margin-right:62px}.settings__item-heading[data-v-2cd59ba0]{display:flex;align-items:center;color:var(--v-text-base);font-size:16.5px}@media(max-width:1000px)and (max-height:450px){.settings__item-heading[data-v-2cd59ba0]{font-size:15px}}.settings__item-label[data-v-2cd59ba0]{margin-top:8px;color:var(--v-text-darken1);font-size:13.5px;line-height:1.6}@media(max-width:1000px)and (max-height:450px){.settings__item-label[data-v-2cd59ba0]{font-size:11px;line-height:1.7}}.settings__item-form[data-v-2cd59ba0]{margin-top:14px}@media(max-width:1000px)and (max-height:450px){.settings__item-form[data-v-2cd59ba0]{font-size:13.5px}}.settings__item-switch[data-v-2cd59ba0]{align-items:center;position:absolute;top:0;right:-74px;bottom:0;margin-top:0}.settings__item p[data-v-2cd59ba0]{margin-bottom:8px}.settings__item p[data-v-2cd59ba0]:last-of-type{margin-bottom:0}.muted-comment-items[data-v-2cd59ba0]{display:flex;flex-direction:column;margin-top:8px}.muted-comment-items .muted-comment-item[data-v-2cd59ba0]{display:flex;align-items:center;padding:6px 0;border-bottom:1px solid var(--v-background-lighten2);transition:background-color .15s ease}.muted-comment-items .muted-comment-item[data-v-2cd59ba0]:last-of-type{border-bottom:none}.muted-comment-items .muted-comment-item__input[data-v-2cd59ba0]{font-size:14px}.muted-comment-items .muted-comment-item__match-type[data-v-2cd59ba0]{max-width:150px;margin-left:12px;font-size:14px}@media(max-width:600px)and (min-height:450.01px){.muted-comment-items .muted-comment-item__match-type[data-v-2cd59ba0]{max-width:115px}}.muted-comment-items .muted-comment-item__delete-button[data-v-2cd59ba0]{display:flex;align-items:center;justify-content:center;width:40px;height:40px;margin-left:6px;border-radius:5px;outline:none;cursor:pointer}.settings__content[data-v-ac48731c]{opacity:1;transition:opacity .4s}.settings__content--loading[data-v-ac48731c]{opacity:0}.niconico-account[data-v-ac48731c]{display:flex;align-items:center;height:120px;padding:20px;border-radius:15px;background:var(--v-background-lighten2)}@media(min-width:1000.01px)and (max-width:1264px)and (max-height:850px){.niconico-account[data-v-ac48731c]{align-items:normal;flex-direction:column;height:auto;padding:16px}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.niconico-account[data-v-ac48731c]{align-items:normal;flex-direction:column;height:auto;padding:16px}.niconico-account .niconico-account-wrapper .niconico-account__info[data-v-ac48731c]{margin-left:16px!important;margin-right:0!important}.niconico-account .niconico-account-wrapper .niconico-account__info-name-text[data-v-ac48731c]{font-size:18.5px}.niconico-account .niconico-account-wrapper .niconico-account__info-description[data-v-ac48731c]{font-size:13.5px}}@media(max-width:1000px)and (max-height:450px){.niconico-account[data-v-ac48731c]{align-items:normal;flex-direction:column;height:auto;padding:16px;border-radius:10px}.niconico-account .niconico-account-wrapper .niconico-account__info[data-v-ac48731c]{margin-right:0!important}}@media(max-width:680px)and (max-height:450px){.niconico-account .niconico-account-wrapper .niconico-account__info[data-v-ac48731c]{margin-left:16px!important}.niconico-account .niconico-account-wrapper .niconico-account__info-name-text[data-v-ac48731c]{font-size:18px}.niconico-account .niconico-account-wrapper .niconico-account__info-description[data-v-ac48731c]{font-size:13px}}@media(max-width:600px)and (min-height:450.01px){.niconico-account[data-v-ac48731c]{align-items:normal;flex-direction:column;height:auto;padding:16px 12px;border-radius:10px}.niconico-account .niconico-account-wrapper .niconico-account__info[data-v-ac48731c]{margin-left:12px!important;margin-right:0!important}.niconico-account .niconico-account-wrapper .niconico-account__info-name-text[data-v-ac48731c]{font-size:17px}.niconico-account .niconico-account-wrapper .niconico-account__info-description[data-v-ac48731c]{font-size:13px}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.niconico-account--anonymous .niconico-account__login[data-v-ac48731c]{margin-top:12px}}@media(max-width:1000px)and (max-height:450px){.niconico-account--anonymous .niconico-account__login[data-v-ac48731c]{margin-top:12px}}@media(max-width:680px)and (max-height:450px){.niconico-account--anonymous .niconico-account-wrapper svg[data-v-ac48731c]{display:none}.niconico-account--anonymous .niconico-account-wrapper .niconico-account__info[data-v-ac48731c]{margin-left:0!important}}@media(max-width:600px)and (min-height:450.01px){.niconico-account--anonymous[data-v-ac48731c]{padding-top:20px}.niconico-account--anonymous .niconico-account__login[data-v-ac48731c]{margin-top:16px}.niconico-account--anonymous .niconico-account-wrapper svg[data-v-ac48731c]{display:none}.niconico-account--anonymous .niconico-account-wrapper .niconico-account__info[data-v-ac48731c]{margin-left:0!important;margin-right:0!important}}.niconico-account-wrapper[data-v-ac48731c]{display:flex;align-items:center;min-width:0;height:80px}@media(max-width:600px)and (min-height:450.01px){.niconico-account-wrapper[data-v-ac48731c]{height:60px}}.niconico-account__icon[data-v-ac48731c]{flex-shrink:0;min-width:80px;height:100%;border-radius:50%;-o-object-fit:cover;object-fit:cover;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2));image-rendering:-webkit-optimize-contrast}@media(max-width:600px)and (min-height:450.01px){.niconico-account__icon[data-v-ac48731c]{width:60px;min-width:60px;height:60px}}.niconico-account__info[data-v-ac48731c]{display:flex;flex-direction:column;min-width:0;margin-left:20px;margin-right:16px}.niconico-account__info-name[data-v-ac48731c]{display:inline-flex;align-items:center;height:33px}@media(max-width:600px)and (min-height:450.01px){.niconico-account__info-name[data-v-ac48731c]{height:auto}}.niconico-account__info-name-text[data-v-ac48731c]{display:inline-block;font-size:20px;color:var(--v-text-base);font-weight:700;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.niconico-account__info-description[data-v-ac48731c]{display:inline-block;margin-top:4px;color:var(--v-text-darken1);font-size:14px}.niconico-account__login[data-v-ac48731c]{border-radius:7px;font-size:16px;letter-spacing:0}@media(min-width:1000.01px)and (max-width:1264px)and (max-height:850px){.niconico-account__login[data-v-ac48731c]{height:50px!important;margin-top:12px;margin-right:auto}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.niconico-account__login[data-v-ac48731c]{height:42px!important;margin-top:8px;margin-right:auto;font-size:14.5px}}@media(max-width:1000px)and (max-height:450px){.niconico-account__login[data-v-ac48731c]{height:42px!important;margin-top:8px;margin-right:auto;font-size:14.5px}}@media(max-width:600px)and (min-height:450.01px){.niconico-account__login[data-v-ac48731c]{height:42px!important;margin-top:16px;margin-right:auto;font-size:14.5px}}.settings__content[data-v-ea90430c]{opacity:1;transition:opacity .4s}.settings__content--loading[data-v-ea90430c]{opacity:0}.twitter-accounts[data-v-ea90430c]{display:flex;flex-direction:column;padding:20px 20px;border-radius:15px;background:var(--v-background-lighten2)}@media(max-width:1000px)and (max-height:450px){.twitter-accounts[data-v-ea90430c]{padding:16px 20px;border-radius:10px}}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts[data-v-ea90430c]{padding:16px 12px;border-radius:10px}}.twitter-accounts__heading[data-v-ea90430c]{display:flex;align-items:center;font-size:18px;font-weight:700}.twitter-accounts__guide[data-v-ea90430c]{display:flex;align-items:center}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-accounts__guide .text-h6[data-v-ea90430c]{font-size:19px!important}}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts__guide .text-h6[data-v-ea90430c]{font-size:17px!important}}@media(max-width:680px)and (max-height:450px){.twitter-accounts__guide svg[data-v-ea90430c]{display:none}}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts__guide svg[data-v-ea90430c]{display:none}}@media(max-width:680px)and (max-height:450px){.twitter-accounts__guide svg+div[data-v-ea90430c]{margin-left:0!important}}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts__guide svg+div[data-v-ea90430c]{margin-left:0!important}}.twitter-accounts .twitter-account[data-v-ea90430c]{display:flex;align-items:center;margin-top:20px}@media(max-width:1000px)and (max-height:450px){.twitter-accounts .twitter-account[data-v-ea90430c]{margin-top:16px}}.twitter-accounts .twitter-account__icon[data-v-ea90430c]{flex-shrink:0;width:70px;height:70px;margin-right:16px;border-radius:50%;-o-object-fit:cover;object-fit:cover;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2));image-rendering:-webkit-optimize-contrast}@media(max-width:1000px)and (max-height:450px){.twitter-accounts .twitter-account__icon[data-v-ea90430c]{width:52px;height:52px}}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts .twitter-account__icon[data-v-ea90430c]{width:48px;height:48px;margin-right:10px}}.twitter-accounts .twitter-account__info[data-v-ea90430c]{display:flex;flex-direction:column;min-width:0;margin-right:16px}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts .twitter-account__info[data-v-ea90430c]{margin-right:10px}}.twitter-accounts .twitter-account__info-name[data-v-ea90430c]{display:inline-flex;align-items:center}.twitter-accounts .twitter-account__info-name-text[data-v-ea90430c]{display:inline-block;color:var(--v-text-base);font-size:20px;font-weight:700;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}@media(max-width:1000px)and (max-height:450px){.twitter-accounts .twitter-account__info-name-text[data-v-ea90430c]{font-size:18px}}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts .twitter-account__info-name-text[data-v-ea90430c]{font-size:16px}}.twitter-accounts .twitter-account__info-screen-name[data-v-ea90430c]{display:inline-block;color:var(--v-text-darken1);font-size:16px}@media(max-width:1000px)and (max-height:450px){.twitter-accounts .twitter-account__info-screen-name[data-v-ea90430c]{font-size:14px}}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts .twitter-account__info-screen-name[data-v-ea90430c]{font-size:13.5px}}.twitter-accounts .twitter-account__login[data-v-ea90430c]{margin-top:20px;margin-left:auto;margin-right:auto;border-radius:7px;font-size:15px;letter-spacing:0}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-accounts .twitter-account__login[data-v-ea90430c]{height:42px!important;font-size:14.5px}}@media(max-width:1000px)and (max-height:450px){.twitter-accounts .twitter-account__login[data-v-ea90430c]{height:42px!important;font-size:14.5px}}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts .twitter-account__login[data-v-ea90430c]{height:42px!important;font-size:14.5px}}.twitter-accounts .twitter-account__logout[data-v-ea90430c]{background:var(--v-gray-base);border-radius:7px;font-size:15px;letter-spacing:0}@media(max-width:1000px)and (max-height:450px){.twitter-accounts .twitter-account__logout[data-v-ea90430c]{width:116px!important}}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts .twitter-account__logout[data-v-ea90430c]{width:100px!important;height:48px!important;border-radius:5px;font-size:14px}.twitter-accounts .twitter-account__logout svg[data-v-ea90430c]{width:20px;margin-right:4px!important}}.channels-container.channels-container--home .v-tabs-bar{height:54px;background:linear-gradient(to bottom,var(--v-background-base) calc(100% - 3px),var(--v-background-lighten1) 3px)}@media(max-width:1000px)and (max-height:450px){.channels-container.channels-container--home .v-tabs-bar{height:46px}}.channels-container.channels-container--home .v-tabs-slider-wrapper{height:3px!important;transition:left .3s cubic-bezier(.25,.8,.5,1)}.channels-container.channels-container--home .v-window__container{min-height:calc(100vh - 180px);min-height:calc(100dvh - 180px)}@media(hover:none){.channels-container.channels-container--home .v-window__container{min-height:auto}}.channels-container.channels-container--home.channels-container--loading .v-tabs-slider-wrapper{transition:none!important}.channels-container[data-v-5395b00e]{display:flex;flex-direction:column;width:100%;margin-left:21px;margin-right:21px;opacity:1;transition:opacity .2s}@media(max-width:600px)and (min-height:450.01px){.channels-container[data-v-5395b00e]{margin-left:0;margin-right:0}}.channels-container--loading[data-v-5395b00e]{opacity:0}.channels-container .channels-tab[data-v-5395b00e]{position:sticky;flex:none;top:65px;padding-top:2px;padding-bottom:12px;background:var(--v-background-base);z-index:1}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-tab[data-v-5395b00e]{top:0;padding-top:0;padding-bottom:8px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-tab[data-v-5395b00e]{top:0;padding-top:0;padding-bottom:10px}}.channels-container .channels-tab .channels-tab__item[data-v-5395b00e]{width:98px;padding:0;color:var(--v-text-base)!important;font-size:16px;text-transform:none}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-tab .channels-tab__item[data-v-5395b00e]{font-size:15px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-tab .channels-tab__item[data-v-5395b00e]{width:auto;font-size:15px}}.channels-container .channels-list[data-v-5395b00e]{padding-bottom:20px;background:transparent!important;overflow:inherit}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list[data-v-5395b00e]{padding-bottom:12px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list[data-v-5395b00e]{padding-left:8px;padding-right:8px;padding-bottom:12px}}.channels-container .channels-list .channels[data-v-5395b00e]{display:grid;grid-template-columns:repeat(auto-fit,minmax(365px,1fr));grid-row-gap:12px;grid-column-gap:16px;justify-content:center;background:var(--v-background-base);will-change:transform}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels[data-v-5395b00e]{grid-row-gap:10px;grid-template-columns:1fr}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels[data-v-5395b00e]{grid-row-gap:8px;grid-template-columns:1fr}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels[data-v-5395b00e]{grid-row-gap:10px;grid-template-columns:1fr}}@media(hover:none){.channels-container .channels-list .channels[data-v-5395b00e]{content-visibility:auto;contain-intrinsic-height:2000px}}@media(min-width:1630px){.channels-container .channels-list .channels[data-v-5395b00e]{grid-template-columns:repeat(auto-fit,445px)}}.channels-container .channels-list .channels.channels--length-0.channels--tab-ピン留め[data-v-5395b00e]{display:flex;min-height:calc(100vh - 180px);min-height:calc(100dvh - 180px)}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels.channels--length-0.channels--tab-ピン留め[data-v-5395b00e]{min-height:calc(100vh - 66px);min-height:calc(100dvh - 66px)}}@media(min-width:1008px){.channels-container .channels-list .channels.channels--length-1[data-v-5395b00e]{margin-right:calc(50% + 8px)}}@media(min-width:1389px){.channels-container .channels-list .channels.channels--length-1[data-v-5395b00e]{margin-right:calc(66.66667% + 10.66667px)}}@media(min-width:1630px){.channels-container .channels-list .channels.channels--length-1[data-v-5395b00e]{margin-right:922px}}@media(min-width:2090px){.channels-container .channels-list .channels.channels--length-1[data-v-5395b00e]{margin-right:1383px}}@media(min-width:1389px){.channels-container .channels-list .channels.channels--length-2[data-v-5395b00e]{margin-right:calc(33.33333% + 5.33333px)}}@media(min-width:1630px){.channels-container .channels-list .channels.channels--length-2[data-v-5395b00e]{margin-right:461px}}@media(min-width:2090px){.channels-container .channels-list .channels.channels--length-2[data-v-5395b00e]{margin-right:922px}.channels-container .channels-list .channels.channels--length-3[data-v-5395b00e]{margin-right:461px}}.channels-container .channels-list .channels .channel[data-v-5395b00e]{display:flex;flex-direction:column;position:relative;height:270px;padding:18px 20px;padding-bottom:19px;border-radius:14px;color:var(--v-text-base);background:var(--v-background-lighten1);transition:background-color .15s;overflow:hidden;text-decoration:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer;content-visibility:auto;contain-intrinsic-height:233px}@media(max-width:1007.9px){.channels-container .channels-list .channels .channel[data-v-5395b00e]{height:auto}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel[data-v-5395b00e]{padding:14px 16px;padding-top:12px;height:auto;border-radius:11px;contain-intrinsic-height:162.25px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel[data-v-5395b00e]{padding:12px 14px;padding-top:10px;height:auto;border-radius:11px;contain-intrinsic-height:125px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel[data-v-5395b00e]{padding:12px 14px;padding-top:10px;height:auto;border-radius:11px;contain-intrinsic-height:162.25px}}.channels-container .channels-list .channels .channel[data-v-5395b00e]:hover{background:var(--v-background-lighten2)}@media(hover:none){.channels-container .channels-list .channels .channel[data-v-5395b00e]:hover{background:var(--v-background-lighten1)}}.channels-container .channels-list .channels .channel .channel__broadcaster[data-v-5395b00e]{display:flex;height:44px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel .channel__broadcaster[data-v-5395b00e]{height:40px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__broadcaster[data-v-5395b00e]{height:29px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__broadcaster[data-v-5395b00e]{height:40px}}.channels-container .channels-list .channels .channel .channel__broadcaster-icon[data-v-5395b00e]{display:inline-block;flex-shrink:0;width:80px;height:44px;border-radius:5px;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2));-o-object-fit:cover;object-fit:cover}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel .channel__broadcaster-icon[data-v-5395b00e]{width:69px;height:40px;border-radius:4px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__broadcaster-icon[data-v-5395b00e]{width:54px;height:29px;border-radius:4px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__broadcaster-icon[data-v-5395b00e]{width:69px;height:40px;border-radius:4px}}.channels-container .channels-list .channels .channel .channel__broadcaster-content[data-v-5395b00e]{display:flex;flex-direction:column;margin-left:16px;width:100%;min-width:0}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel .channel__broadcaster-content[data-v-5395b00e]{margin-left:14px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__broadcaster-content[data-v-5395b00e]{align-items:center;flex-direction:row;margin-left:12px;margin-right:6px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__broadcaster-content[data-v-5395b00e]{margin-left:14px}}.channels-container .channels-list .channels .channel .channel__broadcaster-name[data-v-5395b00e]{flex-shrink:0;font-size:18px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel .channel__broadcaster-name[data-v-5395b00e]{font-size:15.5px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__broadcaster-name[data-v-5395b00e]{font-size:15px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__broadcaster-name[data-v-5395b00e]{font-size:15.5px}}.channels-container .channels-list .channels .channel .channel__broadcaster-status[data-v-5395b00e]{display:flex;position:relative;top:-1.5px;flex-shrink:0;align-items:center;margin-top:2px;font-size:12px;color:var(--v-text-darken1)}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel .channel__broadcaster-status[data-v-5395b00e]{margin-top:2px;font-size:11px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__broadcaster-status[data-v-5395b00e]{margin-top:3px;margin-left:auto;font-size:12px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__broadcaster-status[data-v-5395b00e]{margin-top:2px;font-size:11px}}.channels-container .channels-list .channels .channel .channel__broadcaster-status-force[data-v-5395b00e],.channels-container .channels-list .channels .channel .channel__broadcaster-status-viewers[data-v-5395b00e]{display:flex;align-items:center}@media(max-width:680px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__broadcaster-status-force span[data-v-5395b00e]:nth-child(2),.channels-container .channels-list .channels .channel .channel__broadcaster-status-force span[data-v-5395b00e]:nth-child(4),.channels-container .channels-list .channels .channel .channel__broadcaster-status-viewers span[data-v-5395b00e]:nth-child(2),.channels-container .channels-list .channels .channel .channel__broadcaster-status-viewers span[data-v-5395b00e]:nth-child(4){display:none}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__broadcaster-status-viewers[data-v-5395b00e]{margin-left:8px!important}}.channels-container .channels-list .channels .channel .channel__broadcaster-status-force--festival[data-v-5395b00e]{color:#e7556e}.channels-container .channels-list .channels .channel .channel__broadcaster-status-force--so-many[data-v-5395b00e]{color:#e76b55}.channels-container .channels-list .channels .channel .channel__broadcaster-status-force--many[data-v-5395b00e]{color:#e7a355}.channels-container .channels-list .channels .channel .channel__broadcaster-pin[data-v-5395b00e]{display:flex;align-items:center;justify-content:center;flex-shrink:0;position:relative;top:-5px;right:-5px;width:34px;height:34px;padding:4px;color:var(--v-text-darken1);border-radius:50%;transition:color .15s ease,background-color .15s ease;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__broadcaster-pin[data-v-5395b00e]{top:-1px}}.channels-container .channels-list .channels .channel .channel__broadcaster-pin[data-v-5395b00e]:before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;border-radius:inherit;background-color:currentColor;color:inherit;opacity:0;transition:opacity .2s cubic-bezier(.4,0,.6,1);pointer-events:none}.channels-container .channels-list .channels .channel .channel__broadcaster-pin[data-v-5395b00e]:hover{color:var(--v-text-base)}.channels-container .channels-list .channels .channel .channel__broadcaster-pin[data-v-5395b00e]:hover:before{opacity:.15}.channels-container .channels-list .channels .channel .channel__broadcaster-pin--pinned[data-v-5395b00e]{color:var(--v-primary-base)}.channels-container .channels-list .channels .channel .channel__broadcaster-pin--pinned[data-v-5395b00e]:hover{color:var(--v-primary-lighten1)}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__broadcaster-pin--pinned[data-v-5395b00e]{color:var(--v-secondary-lighten2)}.channels-container .channels-list .channels .channel .channel__broadcaster-pin--pinned[data-v-5395b00e]:hover{color:var(--v-secondary-lighten3)}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__broadcaster-pin--pinned[data-v-5395b00e]{color:var(--v-secondary-lighten2)}.channels-container .channels-list .channels .channel .channel__broadcaster-pin--pinned[data-v-5395b00e]:hover{color:var(--v-secondary-lighten3)}}.channels-container .channels-list .channels .channel .channel__program-present[data-v-5395b00e]{display:flex;flex-direction:column}.channels-container .channels-list .channels .channel .channel__program-present-title-wrapper[data-v-5395b00e]{margin-top:14px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel .channel__program-present-title-wrapper[data-v-5395b00e]{display:flex;flex-direction:column;margin-top:8px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__program-present-title-wrapper[data-v-5395b00e]{display:flex;align-items:center;margin-top:8px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__program-present-title-wrapper[data-v-5395b00e]{display:flex;flex-direction:column;margin-top:8px}}.channels-container .channels-list .channels .channel .channel__program-present-title[data-v-5395b00e]{display:-webkit-box;font-size:16px;font-weight:700;font-feature-settings:"palt" 1;letter-spacing:.07em;overflow:hidden;-webkit-line-clamp:2;-webkit-box-orient:vertical}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel .channel__program-present-title[data-v-5395b00e]{font-size:14px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__program-present-title[data-v-5395b00e]{font-size:14px;-webkit-line-clamp:1}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__program-present-title[data-v-5395b00e]{font-size:14px;-webkit-line-clamp:1}}.channels-container .channels-list .channels .channel .channel__program-present-time[data-v-5395b00e]{margin-top:4px;color:var(--v-text-darken1);font-size:13.5px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel .channel__program-present-time[data-v-5395b00e]{flex-shrink:0;margin-top:2px;font-size:13px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__program-present-time[data-v-5395b00e]{flex-shrink:0;margin-top:0;margin-left:auto;padding-left:10px;font-size:12px}}@media(max-width:680px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__program-present-time[data-v-5395b00e]{font-size:11px;padding-left:6px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__program-present-time[data-v-5395b00e]{flex-shrink:0;margin-top:1px;font-size:12px}}.channels-container .channels-list .channels .channel .channel__program-present-description[data-v-5395b00e]{display:-webkit-box;margin-top:6px;color:var(--v-text-darken1);font-size:10.5px;line-height:175%;overflow-wrap:break-word;font-feature-settings:"palt" 1;letter-spacing:.07em;overflow:hidden;-webkit-line-clamp:3;-webkit-box-orient:vertical}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel .channel__program-present-description[data-v-5395b00e]{margin-top:4px;font-size:11px;line-height:155%;-webkit-line-clamp:2}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__program-present-description[data-v-5395b00e]{margin-top:3px;font-size:10px;line-height:160%;-webkit-line-clamp:2}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__program-present-description[data-v-5395b00e]{margin-top:4px;font-size:10px;line-height:155%;-webkit-line-clamp:2}}.channels-container .channels-list .channels .channel .channel__program-following[data-v-5395b00e]{display:flex;flex-direction:column;color:var(--v-text-base);font-size:12.5px}@media(max-width:1007.9px){.channels-container .channels-list .channels .channel .channel__program-following[data-v-5395b00e]{margin-top:6px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__program-following[data-v-5395b00e]{flex-direction:row;margin-top:4px;font-size:12px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__program-following[data-v-5395b00e]{margin-top:4px;font-size:12px}}.channels-container .channels-list .channels .channel .channel__program-following-title[data-v-5395b00e]{display:flex;align-items:center;min-width:0}.channels-container .channels-list .channels .channel .channel__program-following-title-decorate[data-v-5395b00e]{flex-shrink:0;font-weight:700}.channels-container .channels-list .channels .channel .channel__program-following-title-icon[data-v-5395b00e]{flex-shrink:0;margin-left:3px}.channels-container .channels-list .channels .channel .channel__program-following-title-text[data-v-5395b00e]{margin-left:2px;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.channels-container .channels-list .channels .channel .channel__program-following-time[data-v-5395b00e]{color:var(--v-text-darken1)}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__program-following-time[data-v-5395b00e]{flex-shrink:0;margin-left:auto;padding-left:8px;font-size:11.5px}}@media(max-width:680px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__program-following-time[data-v-5395b00e]{font-size:11px;padding-left:6px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__program-following-time[data-v-5395b00e]{flex-shrink:0;font-size:11.5px}}.channels-container .channels-list .channels .channel .channel__progressbar[data-v-5395b00e]{position:absolute;left:0;right:0;bottom:0;height:4px;background:var(--v-gray-base)}.channels-container .channels-list .channels .channel .channel__progressbar-progress[data-v-5395b00e]{height:4px;background:var(--v-primary-base);transition:width .3s}.channels-container .pinned-container br[data-v-5395b00e]{display:none}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .pinned-container h2[data-v-5395b00e]{font-size:21px!important}.channels-container .pinned-container div[data-v-5395b00e]{font-size:12.5px!important;text-align:center}}@media(max-width:1000px)and (max-height:450px){.channels-container .pinned-container[data-v-5395b00e]{padding-top:12px}.channels-container .pinned-container h2[data-v-5395b00e]{font-size:21px!important}.channels-container .pinned-container div[data-v-5395b00e]{font-size:13px!important;text-align:center}.channels-container .pinned-container div .mt-4[data-v-5395b00e]{margin-top:12px!important}.channels-container .pinned-container div svg[data-v-5395b00e]{width:16px}}@media(max-width:680px)and (max-height:450px){.channels-container .pinned-container h2[data-v-5395b00e]{font-size:16px!important}.channels-container .pinned-container div[data-v-5395b00e]{font-size:10.5px!important}.channels-container .pinned-container div .mt-4[data-v-5395b00e]{margin-top:8px!important}}@media(max-width:600px)and (min-height:450.01px){.channels-container .pinned-container[data-v-5395b00e]{min-height:calc(100vh - 132px);min-height:calc(100dvh - 132px);padding-top:12px}.channels-container .pinned-container br[data-v-5395b00e]{display:inline}.channels-container .pinned-container h2[data-v-5395b00e]{font-size:22px!important;text-align:center}.channels-container .pinned-container div[data-v-5395b00e]{font-size:15px!important;text-align:center}.channels-container .pinned-container div .mt-4[data-v-5395b00e]{margin-top:8px!important}.channels-container .pinned-container div svg[data-v-5395b00e]{width:20px}}.channels-container.channels-container--watch .v-tabs-bar{height:42px;background:linear-gradient(to bottom,var(--v-background-base) calc(100% - 3px),var(--v-background-lighten1) 3px)}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container.channels-container--watch .v-tabs-bar{height:50px}}@media(max-width:1000px)and (max-height:450px){.channels-container.channels-container--watch .v-tabs-bar{height:44px}}@media(max-width:600px)and (min-height:450.01px){.channels-container.channels-container--watch .v-tabs-bar{height:46px}}.channels-container.channels-container--watch .v-tabs-bar .v-slide-group__next,.channels-container.channels-container--watch .v-tabs-bar .v-slide-group__prev{flex:auto!important;min-width:28px!important}.channels-container.channels-container--watch .v-tabs-slider-wrapper{height:3px!important;transition:left .3s cubic-bezier(.25,.8,.5,1)}.channels-container[data-v-3b3e1928]{display:flex;flex-direction:column}.channels-container .channels-tab[data-v-3b3e1928]{position:sticky;flex:none;top:0;padding-left:16px;padding-right:16px;padding-bottom:14px;background:var(--v-background-base);z-index:1}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-tab[data-v-3b3e1928]{padding-left:24px;padding-right:24px;padding-bottom:10px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-tab[data-v-3b3e1928]{padding-bottom:8px;margin-top:0}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-tab[data-v-3b3e1928]{padding-bottom:8px;margin-top:0}}.channels-container .channels-tab .channels-tab__item[data-v-3b3e1928]{min-width:72px!important;padding:0 8px;color:var(--v-text-base)!important;font-size:15px;text-transform:none}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-tab .channels-tab__item[data-v-3b3e1928]{font-size:14.5px}}.channels-container .channels-list-container[data-v-3b3e1928]{overflow-y:auto}.channels-container .channels-list-container .channels-list[data-v-3b3e1928]{padding-left:16px;padding-right:10px;padding-bottom:16px;background:transparent!important;overflow:visible!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list-container .channels-list[data-v-3b3e1928]{padding-left:24px;padding-right:24px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list[data-v-3b3e1928]{padding-bottom:12px}}.channels-container .channels-list-container .channels-list .channels[data-v-3b3e1928]{display:flex;justify-content:center;flex-direction:column;will-change:transform}@media(hover:none){.channels-container .channels-list-container .channels-list .channels[data-v-3b3e1928]{content-visibility:auto;contain-intrinsic-size:319.3px 2000px}}@media(hover:none)and (max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels[data-v-3b3e1928]{contain-intrinsic-size:277.3px 2000px}}@media(min-width:1630px){.channels-container .channels-list-container .channels-list .channels[data-v-3b3e1928]{grid-template-columns:repeat(auto-fit,445px)}}.channels-container .channels-list-container .channels-list .channels .channel[data-v-3b3e1928]{display:flex;flex-direction:column;position:relative;margin-top:12px;padding:10px 12px 14px 12px;border-radius:10px;color:var(--v-text-base);background:var(--v-background-lighten1);transition:background-color .15s;overflow:hidden;text-decoration:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer;content-visibility:auto;contain-intrinsic-size:295.3px 137.3px}.channels-container .channels-list-container .channels-list .channels .channel[data-v-3b3e1928]:first-of-type{margin-top:0}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels .channel[data-v-3b3e1928]{margin-top:8px;padding:8px 12px 12px 12px;border-radius:8px;contain-intrinsic-size:253.3px 107.2px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list-container .channels-list .channels .channel[data-v-3b3e1928]{margin-top:8px;padding:8px 12px 12px 12px;border-radius:8px;contain-intrinsic-size:253.3px 107.2px}}.channels-container .channels-list-container .channels-list .channels .channel[data-v-3b3e1928]:hover{background:var(--v-background-lighten2)}@media(hover:none){.channels-container .channels-list-container .channels-list .channels .channel[data-v-3b3e1928]:hover{background:var(--v-background-lighten1)}}.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster[data-v-3b3e1928]{display:flex;height:28px}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster[data-v-3b3e1928]{height:24px}}.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-icon[data-v-3b3e1928]{display:inline-block;flex-shrink:0;width:48px;height:100%;border-radius:4px;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2));-o-object-fit:cover;object-fit:cover}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-icon[data-v-3b3e1928]{width:46px}}.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-content[data-v-3b3e1928]{display:flex;align-items:center;margin-left:12px;width:100%;min-width:0}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-content[data-v-3b3e1928]{margin-left:8px}}.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-name[data-v-3b3e1928]{font-size:14.5px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-name[data-v-3b3e1928]{font-size:13.5px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-name[data-v-3b3e1928]{font-size:13.5px}}.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-force[data-v-3b3e1928]{display:flex;align-items:center;flex-shrink:0;margin-top:2px;margin-left:auto;padding-left:6px;font-size:12px;color:var(--v-text-darken1)}.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-force--festival[data-v-3b3e1928]{color:#e7556e}.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-force--so-many[data-v-3b3e1928]{color:#e76b55}.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-force--many[data-v-3b3e1928]{color:#e7a355}.channels-container .channels-list-container .channels-list .channels .channel .channel__program-present[data-v-3b3e1928]{display:flex;flex-direction:column}.channels-container .channels-list-container .channels-list .channels .channel .channel__program-present-title[data-v-3b3e1928]{display:-webkit-box;margin-top:8px;font-size:13.5px;font-weight:700;font-feature-settings:"palt" 1;letter-spacing:.07em;overflow:hidden;-webkit-line-clamp:2;-webkit-box-orient:vertical}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels .channel .channel__program-present-title[data-v-3b3e1928]{margin-top:5px;font-size:12.5px;-webkit-line-clamp:1}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list-container .channels-list .channels .channel .channel__program-present-title[data-v-3b3e1928]{margin-top:5px;font-size:12.5px;-webkit-line-clamp:1}}.channels-container .channels-list-container .channels-list .channels .channel .channel__program-present-time[data-v-3b3e1928]{margin-top:4px;color:var(--v-text-darken1);font-size:11.5px}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels .channel .channel__program-present-time[data-v-3b3e1928]{margin-top:1px;font-size:10px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list-container .channels-list .channels .channel .channel__program-present-time[data-v-3b3e1928]{margin-top:1px;font-size:10px}}.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following[data-v-3b3e1928]{display:flex;flex-direction:column;margin-top:4px;color:var(--v-text-darken1);font-size:11.5px}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following[data-v-3b3e1928]{margin-top:2px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following[data-v-3b3e1928]{margin-top:2px}}.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following-title[data-v-3b3e1928]{display:flex;align-items:center}.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following-title-decorate[data-v-3b3e1928]{flex-shrink:0}.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following-title-icon[data-v-3b3e1928]{flex-shrink:0;margin-left:3px}.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following-title-text[data-v-3b3e1928]{margin-left:2px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following-time[data-v-3b3e1928]{margin-top:1px}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following-time[data-v-3b3e1928]{font-size:10px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following-time[data-v-3b3e1928]{font-size:10px}}.channels-container .channels-list-container .channels-list .channels .channel .channel__progressbar[data-v-3b3e1928]{position:absolute;left:0;right:0;bottom:0;height:4px;background:var(--v-gray-base)}.channels-container .channels-list-container .channels-list .channels .channel .channel__progressbar-progress[data-v-3b3e1928]{height:4px;background:var(--v-primary-base);transition:width .3s}.comment-container[data-v-6f8c784f]{display:flex;flex-direction:column}.comment-container .comment-header[data-v-6f8c784f]{display:flex;align-items:center;flex-shrink:0;width:100%;height:26px;padding-left:16px;padding-right:16px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.comment-container .comment-header[data-v-6f8c784f]{margin-top:20px;padding-left:24px;padding-right:24px}}@media(max-width:1000px)and (max-height:450px){.comment-container .comment-header[data-v-6f8c784f]{margin-top:12px}}@media(max-width:600px)and (min-height:450.01px){.comment-container .comment-header[data-v-6f8c784f]{margin-top:14px}}.comment-container .comment-header__title[data-v-6f8c784f]{display:flex;align-items:center;font-size:18.5px;font-weight:700;line-height:145%}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.comment-container .comment-header__title[data-v-6f8c784f]{font-size:19px}}@media(max-width:1000px)and (max-height:450px){.comment-container .comment-header__title[data-v-6f8c784f]{font-size:16.5px}}@media(max-width:600px)and (min-height:450.01px){.comment-container .comment-header__title[data-v-6f8c784f]{font-size:17px}}.comment-container .comment-header__title-icon[data-v-6f8c784f]{margin-bottom:-3px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.comment-container .comment-header__title-icon[data-v-6f8c784f]{width:24px;height:24px}}@media(max-width:1000px)and (max-height:450px){.comment-container .comment-header__title-icon[data-v-6f8c784f]{height:17.5px}}@media(max-width:600px)and (min-height:450.01px){.comment-container .comment-header__title-icon[data-v-6f8c784f]{height:18px}}.comment-container .comment-header__title-text[data-v-6f8c784f]{margin-left:12px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.comment-container .comment-header__title-text[data-v-6f8c784f]{margin-left:16px}}.comment-container .comment-header__button[data-v-6f8c784f]{display:flex;align-items:center;height:26px;padding:0 9px;border-radius:4px;background:var(--v-background-lighten3);font-size:11px;line-height:1.8;letter-spacing:0}.comment-container .comment-list-wrapper[data-v-6f8c784f]{position:relative;width:100%;height:100%;min-height:0;margin-top:16px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.comment-container .comment-list-wrapper[data-v-6f8c784f]{margin-top:20px}}@media(max-width:1000px)and (max-height:450px){.comment-container .comment-list-wrapper[data-v-6f8c784f]{margin-top:12px}}@media(max-width:600px)and (min-height:450.01px){.comment-container .comment-list-wrapper[data-v-6f8c784f]{margin-top:14px}}.comment-container .comment-list-wrapper .comment-list-dropdown[data-v-6f8c784f]{display:inline-block;position:absolute;top:var(--comment-list-dropdown-top,0);right:16px;border-radius:4px;overflow-x:hidden;overflow-y:auto;box-shadow:0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12);opacity:0;visibility:hidden;transition:opacity .15s ease,visibility .15s ease;z-index:8}.comment-container .comment-list-wrapper .comment-list-dropdown--display[data-v-6f8c784f]{opacity:1;visibility:visible}.comment-container .comment-list-wrapper .comment-list-cover[data-v-6f8c784f]{display:none;position:absolute;top:0;left:0;width:100%;height:100%;z-index:7}.comment-container .comment-list-wrapper .comment-list-cover--display[data-v-6f8c784f]{display:block}.comment-container .comment-list-wrapper .comment-list[data-v-6f8c784f]{width:100%;height:100%;padding-left:16px;padding-right:10px;padding-bottom:12px;overflow-y:scroll!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.comment-container .comment-list-wrapper .comment-list[data-v-6f8c784f]{padding-left:24px;padding-right:18px;padding-bottom:0}}@media(max-width:600px)and (min-height:450.01px){.comment-container .comment-list-wrapper .comment-list[data-v-6f8c784f]{padding-bottom:0}}.comment-container .comment-list-wrapper .comment-list .comment[data-v-6f8c784f]{display:flex;position:relative;align-items:center;min-height:28px;padding-top:6px;word-break:break-all}.comment-container .comment-list-wrapper .comment-list .comment--my-post[data-v-6f8c784f]{color:var(--v-secondary-lighten2)}.comment-container .comment-list-wrapper .comment-list .comment__text[data-v-6f8c784f]{font-size:13px}.comment-container .comment-list-wrapper .comment-list .comment__time[data-v-6f8c784f]{flex-shrink:0;margin-left:auto;padding-left:8px;color:var(--v-text-darken1);font-size:13px}.comment-container .comment-list-wrapper .comment-list .comment__icon[data-v-6f8c784f]{width:20px;height:20px;margin-left:8px;border-radius:5px;color:var(--v-text-base);cursor:pointer}.comment-container .comment-list-wrapper .comment-announce[data-v-6f8c784f]{display:flex;align-items:center;justify-content:center;flex-direction:column;position:absolute;top:0;left:0;width:100%;height:100%;padding-left:12px;padding-right:12px}.comment-container .comment-list-wrapper .comment-announce__heading[data-v-6f8c784f]{font-size:20px;font-weight:700}@media(max-width:1000px)and (max-height:450px){.comment-container .comment-list-wrapper .comment-announce__heading[data-v-6f8c784f]{font-size:16px}}.comment-container .comment-list-wrapper .comment-announce__text[data-v-6f8c784f]{margin-top:12px;color:var(--v-text-darken1);font-size:13.5px;text-align:center}@media(max-width:1000px)and (max-height:450px){.comment-container .comment-list-wrapper .comment-announce__text[data-v-6f8c784f]{font-size:12px}}.comment-container .comment-scroll-button[data-v-6f8c784f]{display:flex;align-items:center;justify-content:center;position:absolute;left:0;right:0;bottom:22px;width:42px;height:42px;margin:0 auto;border-radius:50%;background:var(--v-primary-base);transition:background-color .15s,opacity .3s,visibility .3s;visibility:hidden;opacity:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer}.comment-container .comment-scroll-button--display[data-v-6f8c784f]{opacity:1;visibility:visible}.program-container[data-v-12609bdf]{padding-left:16px;padding-right:16px;overflow-y:auto}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.program-container[data-v-12609bdf]{padding-left:24px;padding-right:24px}}.program-container .program-broadcaster[data-v-12609bdf]{display:none;align-items:center;min-width:0}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.program-container .program-broadcaster[data-v-12609bdf]{display:flex;margin-top:20px}}@media(max-width:1000px)and (max-height:450px){.program-container .program-broadcaster[data-v-12609bdf]{display:flex;margin-top:16px}}@media(max-width:600px)and (min-height:450.01px){.program-container .program-broadcaster[data-v-12609bdf]{display:flex;margin-top:16px}}.program-container .program-broadcaster__icon[data-v-12609bdf]{display:inline-block;flex-shrink:0;width:43px;height:24px;border-radius:3px;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2));-o-object-fit:cover;object-fit:cover;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.program-container .program-broadcaster__icon[data-v-12609bdf]{width:58px;height:32px}}@media(max-width:1000px)and (max-height:450px){.program-container .program-broadcaster__icon[data-v-12609bdf]{width:42px;height:23.5px}}@media(max-width:600px)and (min-height:450.01px){.program-container .program-broadcaster__icon[data-v-12609bdf]{width:58px;height:32px}}.program-container .program-broadcaster__number[data-v-12609bdf]{flex-shrink:0;margin-left:12px;font-size:16.5px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.program-container .program-broadcaster__number[data-v-12609bdf]{margin-left:16px;font-size:19px}}.program-container .program-broadcaster__name[data-v-12609bdf]{margin-left:5px;font-size:16.5px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.program-container .program-broadcaster__name[data-v-12609bdf]{margin-left:8px;font-size:19px}}@media(max-width:600px)and (min-height:450.01px){.program-container .program-broadcaster__name[data-v-12609bdf]{font-size:18px}}.program-container .program-info .program-info__title[data-v-12609bdf]{font-size:22px;font-weight:700;line-height:145%;font-feature-settings:"palt" 1;letter-spacing:.05em}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.program-container .program-info .program-info__title[data-v-12609bdf]{margin-top:16px}}@media(max-width:1000px)and (max-height:450px){.program-container .program-info .program-info__title[data-v-12609bdf]{margin-top:10px;font-size:18px}}@media(max-width:600px)and (min-height:450.01px){.program-container .program-info .program-info__title[data-v-12609bdf]{margin-top:16px;font-size:19px}}.program-container .program-info .program-info__time[data-v-12609bdf]{margin-top:8px;color:var(--v-text-darken1);font-size:14px}@media(max-width:1000px)and (max-height:450px){.program-container .program-info .program-info__time[data-v-12609bdf]{font-size:13px}}.program-container .program-info .program-info__description[data-v-12609bdf]{margin-top:12px;color:var(--v-text-darken1);font-size:12px;line-height:168%;overflow-wrap:break-word;font-feature-settings:"palt" 1;letter-spacing:.08em}@media(max-width:1000px)and (max-height:450px){.program-container .program-info .program-info__description[data-v-12609bdf]{margin-top:8px;font-size:11px}}.program-container .program-info .program-info__genre-container[data-v-12609bdf]{display:flex;flex-wrap:wrap;margin-top:10px}.program-container .program-info .program-info__genre-container .program-info__genre[data-v-12609bdf]{display:inline-block;font-size:10.5px;padding:3px;margin-top:4px;margin-right:4px;border-radius:4px;background:var(--v-background-lighten2)}@media(max-width:1000px)and (max-height:450px){.program-container .program-info .program-info__genre-container .program-info__genre[data-v-12609bdf]{font-size:9px}}.program-container .program-info .program-info__next[data-v-12609bdf]{display:flex;align-items:center;margin-top:18px;color:var(--v-text-base);font-size:14px;font-weight:700}@media(max-width:1000px)and (max-height:450px){.program-container .program-info .program-info__next[data-v-12609bdf]{margin-top:14px;font-size:13px}}.program-container .program-info .program-info__next-decorate[data-v-12609bdf]{flex-shrink:0}.program-container .program-info .program-info__next-icon[data-v-12609bdf]{flex-shrink:0;margin-left:3px;font-size:15px}.program-container .program-info .program-info__next-title[data-v-12609bdf]{display:-webkit-box;margin-top:2px;color:var(--v-text-base);font-size:14px;font-weight:700;overflow:hidden;-webkit-line-clamp:2;-webkit-box-orient:vertical}@media(max-width:1000px)and (max-height:450px){.program-container .program-info .program-info__next-title[data-v-12609bdf]{font-size:13px}}.program-container .program-info .program-info__next-time[data-v-12609bdf]{margin-top:3px;color:var(--v-text-darken1);font-size:13.5px}.program-container .program-info .program-info__status[data-v-12609bdf]{display:flex;align-items:center;margin-top:16px;font-size:14px;color:var(--v-text-darken1)}@media(max-width:1000px)and (max-height:450px){.program-container .program-info .program-info__status[data-v-12609bdf]{margin-top:10px;font-size:12px}}.program-container .program-info .program-info__status-force[data-v-12609bdf],.program-container .program-info .program-info__status-viewers[data-v-12609bdf]{display:flex;align-items:center}.program-container .program-info .program-info__status-force--festival[data-v-12609bdf]{color:#e7556e}.program-container .program-info .program-info__status-force--so-many[data-v-12609bdf]{color:#e76b55}.program-container .program-info .program-info__status-force--many[data-v-12609bdf]{color:#e7a355}.program-container .program-detail-container[data-v-12609bdf]{margin-top:24px;margin-bottom:24px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.program-container .program-detail-container[data-v-12609bdf]{margin-top:20px;margin-bottom:20px}}@media(max-width:1000px)and (max-height:450px){.program-container .program-detail-container[data-v-12609bdf]{margin-top:20px;margin-bottom:16px}}.program-container .program-detail-container .program-detail[data-v-12609bdf]{margin-top:16px}.program-container .program-detail-container .program-detail .program-detail__heading[data-v-12609bdf]{font-size:18px}@media(max-width:1000px)and (max-height:450px){.program-container .program-detail-container .program-detail .program-detail__heading[data-v-12609bdf]{font-size:16px}}.program-container .program-detail-container .program-detail .program-detail__text[data-v-12609bdf]{margin-top:8px;color:var(--v-text-darken1);font-size:12px;line-height:168%;overflow-wrap:break-word;white-space:pre-wrap;font-feature-settings:"palt" 1;letter-spacing:.08em}@media(max-width:1000px)and (max-height:450px){.program-container .program-detail-container .program-detail .program-detail__text[data-v-12609bdf]{font-size:11px}}.program-container .program-detail-container .program-detail .program-detail__text[data-v-12609bdf] a:link,.program-container .program-detail-container .program-detail .program-detail__text[data-v-12609bdf] a:visited{color:var(--v-primary-lighten1);text-underline-offset:3px}@media(max-width:1000px)and (max-height:450px){.zoom-capture-modal-container.v-dialog{width:auto!important;max-width:auto!important;aspect-ratio:16/9}}.zoom-capture-modal[data-v-62dbc198]{position:relative}.zoom-capture-modal__image[data-v-62dbc198]{display:block;width:100%;border-radius:11px}.zoom-capture-modal__download[data-v-62dbc198]{display:flex;position:absolute;align-items:center;justify-content:center;right:22px;bottom:20px;width:80px;height:80px;border-radius:50%;color:var(--v-text-base);filter:drop-shadow(0 0 4.5px rgba(0,0,0,.9))}.twitter-container[data-v-62dbc198]{display:flex;flex-direction:column;position:relative;padding-bottom:8px}@media(max-width:600px)and (min-height:450.01px){.twitter-container[data-v-62dbc198]{padding-bottom:0}}.twitter-container.watch-panel__content--active[data-v-62dbc198]{content-visibility:visible!important}.twitter-container.watch-panel__content--active .tab-container .tab-content--active[data-v-62dbc198]{opacity:1;visibility:visible}@media(hover:none){.twitter-container.watch-panel__content--active .tab-container .tab-content--active[data-v-62dbc198]{content-visibility:auto}}.twitter-container .tab-container[data-v-62dbc198]{position:relative;flex-grow:1;min-height:0}.twitter-container .tab-container .tab-content[data-v-62dbc198]{position:absolute;width:100%;height:100%;transition:opacity .2s,visibility .2s;opacity:0;visibility:hidden;overflow-y:scroll}.twitter-container .tab-container .tab-content[data-v-62dbc198]::-webkit-scrollbar{width:6px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tab-container .tab-content[data-v-62dbc198]{padding-top:16px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tab-container .tab-content[data-v-62dbc198]{padding-top:8px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tab-container .tab-content[data-v-62dbc198]{padding-top:8px}}@media(hover:none){.twitter-container .tab-container .tab-content[data-v-62dbc198]{transition:none;content-visibility:hidden}}.twitter-container .tab-container .tab-content .captures[data-v-62dbc198]{display:grid;grid-template-columns:1fr 1fr;grid-row-gap:12px;grid-column-gap:12px;padding-left:12px;padding-right:6px;max-height:100%}@supports(-webkit-touch-callout:none){.twitter-container .tab-container .tab-content .captures[data-v-62dbc198]{padding-right:12px}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tab-container .tab-content .captures[data-v-62dbc198]{grid-template-columns:1fr 1fr 1fr;padding-left:24px;padding-right:24px;grid-row-gap:10px;grid-column-gap:16px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tab-container .tab-content .captures[data-v-62dbc198]{grid-row-gap:8px;grid-column-gap:8px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tab-container .tab-content .captures[data-v-62dbc198]{grid-template-columns:1fr 1fr 1fr;grid-row-gap:10px;grid-column-gap:10px}}.twitter-container .tab-container .tab-content .captures .capture[data-v-62dbc198]{position:relative;height:82px;border-radius:11px;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2));overflow:hidden;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer;content-visibility:auto}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tab-container .tab-content .captures .capture[data-v-62dbc198]{height:90px;border-radius:9px}.twitter-container .tab-container .tab-content .captures .capture .capture__image[data-v-62dbc198]{-o-object-fit:cover;object-fit:cover}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tab-container .tab-content .captures .capture[data-v-62dbc198]{height:74px;border-radius:9px}.twitter-container .tab-container .tab-content .captures .capture .capture__image[data-v-62dbc198]{-o-object-fit:cover;object-fit:cover}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tab-container .tab-content .captures .capture[data-v-62dbc198]{height:82px;border-radius:9px}.twitter-container .tab-container .tab-content .captures .capture .capture__image[data-v-62dbc198]{-o-object-fit:cover;object-fit:cover}}.twitter-container .tab-container .tab-content .captures .capture__image[data-v-62dbc198]{display:block;width:100%;height:100%}.twitter-container .tab-container .tab-content .captures .capture__zoom[data-v-62dbc198]{display:flex;align-items:center;justify-content:center;position:absolute;top:1px;right:3px;width:38px;height:38px;border-radius:50%;filter:drop-shadow(0 0 2.5px rgba(0,0,0,.9));cursor:pointer}.twitter-container .tab-container .tab-content .captures .capture__disabled-cover[data-v-62dbc198],.twitter-container .tab-container .tab-content .captures .capture__selected-number[data-v-62dbc198]{display:none;align-items:center;justify-content:center;position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(30,19,16,.5)}.twitter-container .tab-container .tab-content .captures .capture__selected-number[data-v-62dbc198]{font-size:38px;text-shadow:0 0 2.5px rgba(0,0,0,.9)}.twitter-container .tab-container .tab-content .captures .capture__selected-checkmark[data-v-62dbc198]{display:none;position:absolute;top:6px;left:7px;width:20px;height:20px;color:var(--v-primary-base)}.twitter-container .tab-container .tab-content .captures .capture__selected-border[data-v-62dbc198]{display:none;position:absolute;top:0;left:0;right:0;bottom:0;border-radius:11px;border:4px solid var(--v-primary-base)}.twitter-container .tab-container .tab-content .captures .capture__focused-border[data-v-62dbc198]{display:none;position:absolute;top:0;left:0;right:0;bottom:0;border-radius:11px;border:4px solid var(--v-secondary-base)}.twitter-container .tab-container .tab-content .captures .capture--selected .capture__selected-border[data-v-62dbc198],.twitter-container .tab-container .tab-content .captures .capture--selected .capture__selected-checkmark[data-v-62dbc198],.twitter-container .tab-container .tab-content .captures .capture--selected .capture__selected-number[data-v-62dbc198]{display:flex}.twitter-container .tab-container .tab-content .captures .capture--focused .capture__focused-border[data-v-62dbc198]{display:block}.twitter-container .tab-container .tab-content .captures .capture--disabled[data-v-62dbc198]{cursor:auto}.twitter-container .tab-container .tab-content .captures .capture--disabled .capture__disabled-cover[data-v-62dbc198]{display:block}.twitter-container .tab-container .tab-content .capture-announce[data-v-62dbc198]{display:flex;align-items:center;justify-content:center;flex-direction:column;height:100%;padding-left:12px;padding-right:5px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tab-container .tab-content .capture-announce[data-v-62dbc198]{padding-left:24px;padding-right:24px}}.twitter-container .tab-container .tab-content .capture-announce__heading[data-v-62dbc198]{font-size:20px;font-weight:700}@media(max-width:1000px)and (max-height:450px){.twitter-container .tab-container .tab-content .capture-announce__heading[data-v-62dbc198]{font-size:16px}}.twitter-container .tab-container .tab-content .capture-announce__text[data-v-62dbc198]{margin-top:12px;color:var(--v-text-darken1);font-size:13.5px;text-align:center}@media(max-width:1000px)and (max-height:450px){.twitter-container .tab-container .tab-content .capture-announce__text[data-v-62dbc198]{font-size:12px}}.twitter-container .tab-button-container[data-v-62dbc198]{display:flex;flex-shrink:0;-moz-column-gap:7px;column-gap:7px;height:40px;margin-left:12px;margin-right:12px;padding-top:8px;padding-bottom:6px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tab-button-container[data-v-62dbc198]{height:40px;margin-left:24px;margin-right:24px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tab-button-container[data-v-62dbc198]{height:38px;margin-left:8px;margin-right:8px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tab-button-container[data-v-62dbc198]{height:38px}}.twitter-container .tab-button-container .tab-button[data-v-62dbc198]{display:flex;align-items:center;justify-content:center;flex:1;background:var(--v-background-lighten2);border-radius:7px;font-size:11px;transition:background-color .15s ease;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tab-button-container .tab-button[data-v-62dbc198]{font-size:11.5px;border-radius:7px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tab-button-container .tab-button[data-v-62dbc198]{font-size:10.5px;border-radius:6px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tab-button-container .tab-button[data-v-62dbc198]{font-size:10.5px;border-radius:6px}}.twitter-container .tab-button-container .tab-button--active[data-v-62dbc198]{background:var(--v-twitter-base)}.twitter-container .tab-button-container .tab-button__text[data-v-62dbc198]{margin-left:4px;margin-right:2px;line-height:2}.twitter-container .tweet-form[data-v-62dbc198]{display:flex;position:relative;flex-direction:column;flex-shrink:0;height:136px;margin-left:12px;margin-right:12px;border-radius:12px;border-bottom-left-radius:7px;border-bottom-right-radius:7px;background:var(--v-background-lighten1);transition:box-shadow .09s ease}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tweet-form[data-v-62dbc198]{margin-left:24px;margin-right:24px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form[data-v-62dbc198]{height:96px;margin-left:8px;margin-right:8px;border-radius:6px;border-bottom-left-radius:5px;border-bottom-right-radius:5px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form[data-v-62dbc198]{height:96px;border-radius:6px;border-bottom-left-radius:5px;border-bottom-right-radius:5px}}.twitter-container .tweet-form--focused[data-v-62dbc198]{box-shadow:0 0 0 3.5px rgba(79,130,230,.6)}.twitter-container .tweet-form--virtual-keyboard-display[data-v-62dbc198]{position:relative;bottom:calc(env(keyboard-inset-height, 0px) - 77px)}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tweet-form--virtual-keyboard-display[data-v-62dbc198]{bottom:calc(env(keyboard-inset-height, 0px) - 40px)!important}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form--virtual-keyboard-display[data-v-62dbc198]{bottom:calc(env(keyboard-inset-height, 0px) - 34px)}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form--virtual-keyboard-display[data-v-62dbc198]{bottom:calc(env(keyboard-inset-height, 0px) - 90px)!important}}.twitter-container .tweet-form__hashtag[data-v-62dbc198]{display:block;position:relative;height:19px;margin-top:12px;margin-left:12px;margin-right:12px}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__hashtag[data-v-62dbc198]{height:16px;margin-top:8px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__hashtag[data-v-62dbc198]{height:16px;margin-top:8px}}.twitter-container .tweet-form__hashtag-form[data-v-62dbc198]{display:block;width:calc(100% - 24px);height:100%;flex-grow:1;font-size:12.5px;color:var(--v-twitter-lighten2);outline:none}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__hashtag-form[data-v-62dbc198]{width:calc(100% - 22px);font-size:12px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__hashtag-form[data-v-62dbc198]{width:calc(100% - 22px);font-size:12px}}@supports(-webkit-touch-callout:none){@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__hashtag-form[data-v-62dbc198]{width:calc(133.3% - 32px);height:25.328px;font-size:16px;transform:scale(.75);transform-origin:0 0}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__hashtag-form[data-v-62dbc198]{width:calc(133.3% - 32px);height:25.328px;font-size:16px;transform:scale(.75);transform-origin:0 0}}}.twitter-container .tweet-form__hashtag-form[data-v-62dbc198]::-moz-placeholder{color:rgba(65,165,241,.6)}.twitter-container .tweet-form__hashtag-form[data-v-62dbc198]::placeholder{color:rgba(65,165,241,.6)}.twitter-container .tweet-form__hashtag-list-button[data-v-62dbc198]{display:flex;position:absolute;align-items:center;justify-content:center;top:-8px;right:-8px;width:34px;height:34px;padding:6px;border-radius:50%;color:var(--v-twitter-lighten2);cursor:pointer}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__hashtag-list-button[data-v-62dbc198]{right:-11px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__hashtag-list-button[data-v-62dbc198]{right:-11px}}.twitter-container .tweet-form__textarea[data-v-62dbc198]{display:block;flex-grow:1;margin-top:8px;margin-left:12px;margin-right:12px;font-size:12.5px;color:var(--v-text-base);word-break:break-all;resize:none;outline:none}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__textarea[data-v-62dbc198]{margin-top:6px;font-size:12px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__textarea[data-v-62dbc198]{margin-top:6px;font-size:12px}}@supports(-webkit-touch-callout:none){@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__textarea[data-v-62dbc198]{position:absolute;top:24px;left:-2px;min-width:calc(128% - 25px);min-height:34px;font-size:16px;transform:scale(.78125);transform-origin:0 0}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__textarea[data-v-62dbc198]{position:absolute;top:24px;left:-2px;min-width:calc(128% - 25px);min-height:34px;font-size:16px;transform:scale(.78125);transform-origin:0 0}}}.twitter-container .tweet-form__textarea[data-v-62dbc198]::-moz-placeholder{color:var(--v-text-darken2)}.twitter-container .tweet-form__textarea[data-v-62dbc198]::placeholder{color:var(--v-text-darken2)}.twitter-container .tweet-form__control[data-v-62dbc198]{display:flex;align-items:center;height:32px;margin-top:6px}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__control[data-v-62dbc198]{height:26px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__control[data-v-62dbc198]{height:26px}}@supports(-webkit-touch-callout:none){@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__control[data-v-62dbc198]{margin-top:auto}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__control[data-v-62dbc198]{margin-top:auto}}}.twitter-container .tweet-form__control .account-button[data-v-62dbc198]{display:flex;align-items:center;width:183px;height:100%;border-radius:7px;font-size:13px;color:var(--v-text-base);background:var(--v-background-lighten2);-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tweet-form__control .account-button[data-v-62dbc198]{width:200px;border-radius:5px;font-size:11.5px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__control .account-button[data-v-62dbc198]{width:156px;border-radius:5px;font-size:11px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__control .account-button[data-v-62dbc198]{width:auto;flex-grow:1;border-radius:5px;font-size:11.5px}}.twitter-container .tweet-form__control .account-button--no-login .account-button__screen-name[data-v-62dbc198]{font-weight:500}.twitter-container .tweet-form__control .account-button--no-login .account-button__menu[data-v-62dbc198]{display:none}.twitter-container .tweet-form__control .account-button__icon[data-v-62dbc198]{display:block;width:32px;height:100%;border-radius:7px;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2))}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__control .account-button__icon[data-v-62dbc198]{width:26px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__control .account-button__icon[data-v-62dbc198]{width:26px}}.twitter-container .tweet-form__control .account-button__screen-name[data-v-62dbc198]{flex-grow:1;line-height:2;text-align:center;font-weight:700}.twitter-container .tweet-form__control .account-button__menu[data-v-62dbc198]{margin-right:4px}.twitter-container .tweet-form__control .limit-meter[data-v-62dbc198]{display:flex;align-items:center;justify-content:center;flex-direction:column;flex-grow:1;row-gap:.5px;font-size:10px;color:var(--v-text-darken1);-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tweet-form__control .limit-meter[data-v-62dbc198]{flex-grow:1;flex-direction:row;width:auto}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__control .limit-meter[data-v-62dbc198]{font-size:9px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__control .limit-meter[data-v-62dbc198]{flex-grow:unset;flex-direction:row;width:88px}}.twitter-container .tweet-form__control .limit-meter__content[data-v-62dbc198]{display:flex;align-items:center;justify-content:center}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tweet-form__control .limit-meter__content[data-v-62dbc198]:nth-child(2){margin-left:28px;padding-right:3px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__control .limit-meter__content[data-v-62dbc198]:nth-child(2){margin-top:-2.5px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__control .limit-meter__content[data-v-62dbc198]:nth-child(2){margin-left:6px}}.twitter-container .tweet-form__control .limit-meter__content svg[data-v-62dbc198]{width:14px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tweet-form__control .limit-meter__content svg[data-v-62dbc198]{width:16px;height:16px}}.twitter-container .tweet-form__control .limit-meter__content span[data-v-62dbc198]{width:16px;margin-left:5px;text-align:center;font-weight:700}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tweet-form__control .limit-meter__content span[data-v-62dbc198]{width:25px;margin-left:8px;font-size:15px}}.twitter-container .tweet-form__control .limit-meter__content--yellow[data-v-62dbc198]{color:var(--v-warning-base)}.twitter-container .tweet-form__control .limit-meter__content--red[data-v-62dbc198]{color:var(--v-error-base)}.twitter-container .tweet-form__control .tweet-button[data-v-62dbc198]{display:flex;align-items:center;justify-content:center;width:94px;height:100%;border-radius:7px;font-size:12.5px;line-height:2;color:var(--v-text-base);background:var(--v-twitter-base);-webkit-user-select:none;-moz-user-select:none;user-select:none;outline:none;cursor:pointer}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tweet-form__control .tweet-button[data-v-62dbc198]{width:200px;border-radius:5px;font-size:11.8px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__control .tweet-button[data-v-62dbc198]{width:86px;border-radius:5px;font-size:11.8px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__control .tweet-button[data-v-62dbc198]{width:100px;border-radius:5px;font-size:11.8px}}.twitter-container .tweet-form__control .tweet-button[disabled][data-v-62dbc198]{opacity:.7;cursor:auto}.twitter-container .hashtag-list[data-v-62dbc198]{position:absolute;left:12px;right:12px;bottom:149px;max-height:calc(100vh - 239px);max-height:calc(100dvh - 239px);padding:12px 4px;padding-bottom:10px;border-radius:7px;-webkit-clip-path:inset(0 0 0 0 round 7px);clip-path:inset(0 0 0 0 round 7px);background:var(--v-background-lighten2);box-shadow:0 3px 4px rgba(0,0,0,.53);transition:opacity .15s ease,visibility .15s ease;opacity:0;visibility:hidden;overflow-y:auto;z-index:2}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .hashtag-list[data-v-62dbc198]{left:24px;right:24px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .hashtag-list[data-v-62dbc198]{left:8px;right:8px;bottom:110px;max-height:calc(100vh - 152px);max-height:calc(100dvh - 152px);padding:6px 4px;border-radius:6px;-webkit-clip-path:inset(0 0 0 0 round 6px);clip-path:inset(0 0 0 0 round 6px)}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .hashtag-list[data-v-62dbc198]{bottom:102px;max-height:calc(100% - 110px);padding:8px 4px;border-radius:6px;-webkit-clip-path:inset(0 0 0 0 round 6px);clip-path:inset(0 0 0 0 round 6px)}}.twitter-container .hashtag-list--display[data-v-62dbc198]{opacity:1;visibility:visible}.twitter-container .hashtag-list--virtual-keyboard-display[data-v-62dbc198]{bottom:calc(env(keyboard-inset-height, 0px) - 74px)!important;max-height:calc(100vh - env(keyboard-inset-height, 0px) - 16px)!important;max-height:calc(100dvh - env(keyboard-inset-height, 0px) - 16px)!important}@media(max-width:1000px)and (max-height:450px){.twitter-container .hashtag-list--virtual-keyboard-display[data-v-62dbc198]{bottom:calc(env(keyboard-inset-height, 0px) - 26px)!important}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .hashtag-list--virtual-keyboard-display[data-v-62dbc198]{bottom:calc(env(keyboard-inset-height, 0px) - 90px)!important;max-height:calc(100% - env(keyboard-inset-height, 0px) + 82px)!important}}@media(max-width:1000px)and (max-height:450px){.twitter-container .hashtag-list[data-v-62dbc198]::-webkit-scrollbar{width:4px}}.twitter-container .hashtag-list[data-v-62dbc198]::-webkit-scrollbar-track{background:var(--v-background-lighten2)}.twitter-container .hashtag-list[data-v-62dbc198]::-webkit-scrollbar-thumb,.twitter-container .hashtag-list[data-v-62dbc198]::-webkit-scrollbar-thumb:hover{background:var(--v-gray-base)}@supports(-webkit-touch-callout:none){@media(max-width:1000px)and (max-height:450px){.twitter-container .hashtag-list[data-v-62dbc198]::-webkit-scrollbar{width:.1px;-webkit-appearance:none}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .hashtag-list[data-v-62dbc198]::-webkit-scrollbar{width:.1px;-webkit-appearance:none}}}.twitter-container .hashtag-list .hashtag-heading[data-v-62dbc198]{display:flex;align-items:center;font-weight:700;padding-left:8px;padding-right:4px}@media(max-width:1000px)and (max-height:450px){.twitter-container .hashtag-list .hashtag-heading[data-v-62dbc198]{padding-left:4px;padding-right:2px}}.twitter-container .hashtag-list .hashtag-heading__text[data-v-62dbc198]{display:flex;align-items:center;flex-grow:1;font-size:14px}.twitter-container .hashtag-list .hashtag-heading__add-button[data-v-62dbc198]{display:flex;align-items:center;font-size:13px;padding:4px 8px;border-radius:5px;outline:none;cursor:pointer}.twitter-container .hashtag-list .hashtag-container[data-v-62dbc198]{display:flex;flex-direction:column}.twitter-container .hashtag-list .hashtag-container .hashtag[data-v-62dbc198]{display:flex;position:relative!important;align-items:center;padding-top:1.5px;padding-bottom:1.5px;padding-left:8px;padding-right:4px;border-radius:7px;transition:background-color .15s ease;cursor:pointer}@media(max-width:1000px)and (max-height:450px){.twitter-container .hashtag-list .hashtag-container .hashtag[data-v-62dbc198]{padding-top:0;padding-bottom:0;padding-left:4px;padding-right:2px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .hashtag-list .hashtag-container .hashtag[data-v-62dbc198]{padding-top:0;padding-bottom:0}}.twitter-container .hashtag-list .hashtag-container .hashtag[data-v-62dbc198]:first-of-type{margin-top:6px}@media(max-width:600px)and (min-height:450.01px){.twitter-container .hashtag-list .hashtag-container .hashtag[data-v-62dbc198]:first-of-type{margin-top:0}}.twitter-container .hashtag-list .hashtag-container .hashtag[data-v-62dbc198]:hover{background:hsla(0,0%,100%,.1)}@media(hover:none){.twitter-container .hashtag-list .hashtag-container .hashtag[data-v-62dbc198]:hover{background:transparent}}.twitter-container .hashtag-list .hashtag-container .hashtag--editing[data-v-62dbc198]:hover{background:transparent}.twitter-container .hashtag-list .hashtag-container .hashtag--editing .hashtag__input[data-v-62dbc198]{box-shadow:0 0 0 3.5px rgba(79,130,230,.6);cursor:text}.twitter-container .hashtag-list .hashtag-container .hashtag__input[data-v-62dbc198]{display:block;flex-grow:1;border-radius:2px;color:var(--v-twitter-lighten2);opacity:1;outline:none;cursor:pointer;transition:box-shadow .09s ease;margin-right:4px;font-size:12.5px}@supports(-webkit-touch-callout:none){@media(max-width:1000px)and (max-height:450px){.twitter-container .hashtag-list .hashtag-container .hashtag__input[data-v-62dbc198]{position:absolute!important;left:-26px!important;width:calc(100% - 6px);margin-right:0;font-size:16px;transform:scale(.78125)}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .hashtag-list .hashtag-container .hashtag__input[data-v-62dbc198]{position:absolute!important;left:-26px!important;width:calc(100% - 21px);margin-right:0;font-size:16px;transform:scale(.78125)}}}.twitter-container .hashtag-list .hashtag-container .hashtag__edit-button[data-v-62dbc198]{margin-left:auto}.twitter-container .hashtag-list .hashtag-container .hashtag__delete-button[data-v-62dbc198],.twitter-container .hashtag-list .hashtag-container .hashtag__edit-button[data-v-62dbc198],.twitter-container .hashtag-list .hashtag-container .hashtag__sort-handle[data-v-62dbc198]{display:flex;align-items:center;justify-content:center;width:19px;height:27px;border-radius:5px;outline:none;cursor:pointer}@media(max-width:600px)and (min-height:450.01px){.twitter-container .hashtag-list .hashtag-container .hashtag__delete-button[data-v-62dbc198],.twitter-container .hashtag-list .hashtag-container .hashtag__edit-button[data-v-62dbc198],.twitter-container .hashtag-list .hashtag-container .hashtag__sort-handle[data-v-62dbc198]{width:25px}}.twitter-container .hashtag-list .hashtag-container .hashtag__sort-handle[data-v-62dbc198]{cursor:move}.twitter-container .twitter-account-list[data-v-62dbc198]{position:absolute;left:12px;right:12px;bottom:48px;max-height:calc(100vh - 137px);max-height:calc(100dvh - 137px);border-radius:7px;-webkit-clip-path:inset(0 0 0 0 round 7px);clip-path:inset(0 0 0 0 round 7px);background:var(--v-background-lighten2);box-shadow:0 3px 4px rgba(0,0,0,.53);transition:opacity .15s ease,visibility .15s ease;opacity:0;visibility:hidden;overflow-y:auto;z-index:3}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .twitter-account-list[data-v-62dbc198]{left:24px;right:24px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .twitter-account-list[data-v-62dbc198]{left:8px;right:8px;bottom:40px;max-height:calc(100vh - 82px);max-height:calc(100dvh - 82px);border-radius:6px;-webkit-clip-path:inset(0 0 0 0 round 6px);clip-path:inset(0 0 0 0 round 6px)}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .twitter-account-list[data-v-62dbc198]{bottom:32px;max-height:calc(100% - 40px);border-radius:6px;-webkit-clip-path:inset(0 0 0 0 round 6px);clip-path:inset(0 0 0 0 round 6px)}}.twitter-container .twitter-account-list--display[data-v-62dbc198]{opacity:1;visibility:visible}.twitter-container .twitter-account-list[data-v-62dbc198]::-webkit-scrollbar-track{background:var(--v-background-lighten2)}.twitter-container .twitter-account-list[data-v-62dbc198]::-webkit-scrollbar-thumb,.twitter-container .twitter-account-list[data-v-62dbc198]::-webkit-scrollbar-thumb:hover{background:var(--v-gray-base)}.twitter-container .twitter-account-list .twitter-account[data-v-62dbc198]{display:flex;align-items:center;padding:12px 12px;border-radius:7px;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer}@media(max-width:1000px)and (max-height:450px){.twitter-container .twitter-account-list .twitter-account[data-v-62dbc198]{padding:8px 12px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .twitter-account-list .twitter-account[data-v-62dbc198]{padding:8px 12px}}.twitter-container .twitter-account-list .twitter-account__icon[data-v-62dbc198]{display:block;width:50px;height:50px;border-radius:50%}@media(max-width:1000px)and (max-height:450px){.twitter-container .twitter-account-list .twitter-account__icon[data-v-62dbc198]{width:36px;height:36px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .twitter-account-list .twitter-account__icon[data-v-62dbc198]{width:36px;height:36px}}.twitter-container .twitter-account-list .twitter-account__info[data-v-62dbc198]{display:flex;flex-direction:column;flex-grow:1;min-width:0;margin-left:12px}.twitter-container .twitter-account-list .twitter-account__name[data-v-62dbc198]{font-size:17px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}@media(max-width:1000px)and (max-height:450px){.twitter-container .twitter-account-list .twitter-account__name[data-v-62dbc198]{font-size:14px;line-height:1.3}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .twitter-account-list .twitter-account__name[data-v-62dbc198]{font-size:14px;line-height:1.3}}.twitter-container .twitter-account-list .twitter-account__screen-name[data-v-62dbc198]{color:var(--v-text-darken1);font-size:14px}@media(max-width:1000px)and (max-height:450px){.twitter-container .twitter-account-list .twitter-account__screen-name[data-v-62dbc198]{font-size:13px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .twitter-account-list .twitter-account__screen-name[data-v-62dbc198]{font-size:13px}}.twitter-container .twitter-account-list .twitter-account__check[data-v-62dbc198]{flex-shrink:0;color:var(--v-twitter-lighten1)}.watch-player__dplayer svg circle,.watch-player__dplayer svg path{fill:var(--v-text-base)!important}.watch-player__dplayer .dplayer-video-wrap{background:transparent!important}.watch-player__dplayer .dplayer-video-wrap .dplayer-video-wrap-aspect{transition:opacity .2s cubic-bezier(.4,.38,.49,.94);opacity:1}.watch-player__dplayer .dplayer-video-wrap .dplayer-danmaku{max-width:100%;max-height:calc(100% - var(--comment-area-vertical-margin, 0px));aspect-ratio:var(--comment-area-aspect-ratio,16/9);transition:max-height .5s cubic-bezier(.42,.19,.53,.87),aspect-ratio .5s cubic-bezier(.42,.19,.53,.87);will-change:aspect-ratio;overflow:hidden}.watch-player__dplayer .dplayer-video-wrap .dplayer-danloading,.watch-player__dplayer .dplayer-video-wrap .dplayer-loading-icon{display:none!important}.watch-player__dplayer .dplayer-controller-mask{height:82px!important;background:linear-gradient(180deg,transparent,#000000cf)!important;opacity:0!important;visibility:hidden;transition:opacity .3s ease,visibility .3s ease!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-player__dplayer .dplayer-controller-mask{height:66px!important}}@media(max-width:1000px)and (max-height:450px){.watch-player__dplayer .dplayer-controller-mask{height:66px!important}}@media(max-width:600px)and (min-height:450.01px){.watch-player__dplayer .dplayer-controller-mask{height:66px!important}}.watch-player__dplayer .dplayer-controller{padding-left:86px!important;padding-bottom:6px!important;transition:opacity .3s ease,visibility .3s ease;opacity:0!important;visibility:hidden}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-player__dplayer .dplayer-controller{padding-left:18px!important}}@media(max-width:1000px)and (max-height:450px){.watch-player__dplayer .dplayer-controller{padding-left:18px!important}}@media(max-width:600px)and (min-height:450.01px){.watch-player__dplayer .dplayer-controller{padding-left:18px!important}}.watch-player__dplayer .dplayer-controller .dplayer-live-badge,.watch-player__dplayer .dplayer-controller .dplayer-time{color:var(--v-text-base)!important}.watch-player__dplayer .dplayer-controller .dplayer-volume-bar{background:var(--v-text-base)!important}.watch-player__dplayer .dplayer-controller .dplayer-icons{bottom:auto!important}.watch-player__dplayer .dplayer-controller .dplayer-icons.dplayer-icons-right{right:22px!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-player__dplayer .dplayer-controller .dplayer-icons.dplayer-icons-right{right:11px!important}}@media(max-width:1000px)and (max-height:450px){.watch-player__dplayer .dplayer-controller .dplayer-icons.dplayer-icons-right{right:11px!important}}@media(max-width:600px)and (min-height:450.01px){.watch-player__dplayer .dplayer-controller .dplayer-icons.dplayer-icons-right{right:11px!important}}.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-full-in-icon{display:none!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-pip-icon:after{left:25%}.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-full-icon:after{left:-20%}}@media(max-width:1000px)and (max-height:450px){.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-pip-icon:after{left:25%}.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-full-icon:after{left:-20%}}@media(max-width:600px)and (min-height:450.01px){.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-pip-icon:after{left:25%}.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-full-icon:after{left:-20%}}.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-capture-icon,.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-comment-capture-icon{transition:background-color .08s ease;border-radius:6px}.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-capture-icon.dplayer-capturing,.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-comment-capture-icon.dplayer-capturing{background:var(--v-secondary-lighten1)}.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-capture-icon.dplayer-capturing .dplayer-icon-content,.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-comment-capture-icon.dplayer-capturing .dplayer-icon-content{opacity:1}.watch-player__dplayer .dplayer-controller .dplayer-comment-box{transition:opacity .3s ease,visibility .3s ease!important}.watch-player__dplayer .dplayer-controller .dplayer-comment-box .dplayer-comment-setting-icon{z-index:5}.watch-player__dplayer .dplayer-controller .dplayer-comment-box .dplayer-comment-input{transition:box-shadow .09s ease;-moz-appearance:none;appearance:none;-webkit-appearance:none}.watch-player__dplayer .dplayer-controller .dplayer-comment-box .dplayer-comment-input:focus{box-shadow:0 0 0 3.5px rgba(79,130,230,.6)}@supports(-webkit-touch-callout:none){@media(max-width:1000px)and (max-height:450px){.watch-player__dplayer .dplayer-controller .dplayer-comment-box .dplayer-comment-input{width:114.2857%!important;height:114.2857%!important;font-size:16px!important;transform:scale(.875);transform-origin:0 0}}@media(max-width:600px)and (min-height:450.01px){.watch-player__dplayer .dplayer-controller .dplayer-comment-box .dplayer-comment-input{width:114.2857%!important;height:114.2857%!important;font-size:16px!important;transform:scale(.875);transform-origin:0 0}}}.watch-player__dplayer .dplayer-notice{padding:16px 22px!important;margin-right:30px;border-radius:4px!important;font-size:15px!important;line-height:1.6}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-player__dplayer .dplayer-notice{top:auto;left:16px!important;padding:12px 16px!important;margin-right:16px;font-size:13.5px!important}}@media(max-width:1000px)and (max-height:450px){.watch-player__dplayer .dplayer-notice{padding:12px 16px!important;margin-right:16px;font-size:13.5px!important}}@media(max-width:600px)and (min-height:450.01px){.watch-player__dplayer .dplayer-notice{top:auto;left:16px!important;padding:12px 16px!important;margin-right:16px;font-size:13.5px!important}}.watch-player__dplayer .dplayer-info-panel{transition:top .3s,left .3s}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-player__dplayer .dplayer-setting-box{height:calc(100% - 60px)!important}}@media(max-width:600px)and (min-height:450.01px){.watch-player__dplayer .dplayer-setting-box{height:calc(100% - 60px)!important}}.watch-player__dplayer .dplayer-setting-box .dplayer-setting-audio-panel .dplayer-setting-audio-item.dplayer-setting-audio-item--disabled{pointer-events:none}.watch-player__dplayer .dplayer-setting-box .dplayer-setting-audio-panel .dplayer-setting-audio-item.dplayer-setting-audio-item--disabled .dplayer-label{color:#aaa}.watch-player__dplayer .dplayer-comment-setting-box .dplayer-comment-setting-title{color:var(--v-text-base)}.watch-player__dplayer .dplayer-comment-setting-box .dplayer-comment-setting-size span,.watch-player__dplayer .dplayer-comment-setting-box .dplayer-comment-setting-type span{border:1px solid --v-text-base}.watch-player__dplayer .dplayer-comment-setting-box .dplayer-comment-setting-size input:checked+span,.watch-player__dplayer .dplayer-comment-setting-box .dplayer-comment-setting-type input:checked+span{background:var(--v-text-base)}.watch-player__dplayer.dplayer-mobile .dplayer-controller{padding-left:98px!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-player__dplayer.dplayer-mobile .dplayer-controller{padding-left:18px!important}}@media(max-width:1000px)and (max-height:450px){.watch-player__dplayer.dplayer-mobile .dplayer-controller{padding-left:18px!important}}@media(max-width:600px)and (min-height:450.01px){.watch-player__dplayer.dplayer-mobile .dplayer-controller{padding-left:18px!important}}.watch-player__dplayer.dplayer-mobile.dplayer-hide-controller .dplayer-controller{transform:none!important}.watch-player--loading .dplayer-video-wrap-aspect{opacity:0!important}:root .dplayer-icon:hover .dplayer-icon-content,_::-webkit-full-page-media,_:future{opacity:.8!important}.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller,.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller-mask{opacity:1!important;visibility:visible!important}.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller .dplayer-comment-box,.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller-mask .dplayer-comment-box{left:88px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller .dplayer-comment-box,.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller-mask .dplayer-comment-box{left:16px}}@media(max-width:1000px)and (max-height:450px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller .dplayer-comment-box,.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller-mask .dplayer-comment-box{left:16px}}@media(max-width:600px)and (min-height:450.01px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller .dplayer-comment-box,.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller-mask .dplayer-comment-box{left:16px}}.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-notice{left:98px;bottom:62px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-notice{left:16px;bottom:62px!important}}@media(max-width:1000px)and (max-height:450px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-notice{left:16px}}@media(max-width:600px)and (min-height:450.01px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-notice{left:16px;bottom:62px!important}}.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-info-panel{top:82px;left:98px}@media(min-width:1000.01px)and (max-width:1264px)and (max-height:850px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-info-panel{left:16px}}@media(max-width:1000px)and (max-height:450px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-info-panel{left:16px}}@media(max-width:600px)and (min-height:450.01px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-info-panel{left:16px}}.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-comment-setting-box{left:88px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-comment-setting-box{left:16px}}@media(max-width:1000px)and (max-height:450px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-comment-setting-box{left:16px}}@media(max-width:600px)and (min-height:450.01px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-comment-setting-box{left:16px}}.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-mobile .dplayer-mobile-icon-wrap{opacity:.7!important;visibility:visible!important}.watch-container:not(.watch-container--control-display) .watch-player__dplayer .dplayer-danmaku{max-height:100%!important;aspect-ratio:16/9!important}.watch-container:not(.watch-container--control-display) .watch-player__dplayer .dplayer-notice{bottom:20px!important}.watch-container.watch-container--fullscreen .watch-player__dplayer .dplayer-controller{padding-left:20px!important}.watch-container.watch-container--fullscreen .watch-player__dplayer.dplayer-mobile .dplayer-controller{padding-left:30px!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container.watch-container--fullscreen .watch-player__dplayer.dplayer-mobile .dplayer-controller{padding-left:16px!important}}@media(max-width:1000px)and (max-height:450px){.watch-container.watch-container--fullscreen .watch-player__dplayer.dplayer-mobile .dplayer-controller{padding-left:16px!important}}@media(max-width:600px)and (min-height:450.01px){.watch-container.watch-container--fullscreen .watch-player__dplayer.dplayer-mobile .dplayer-controller{padding-left:16px!important}}.watch-container.watch-container--fullscreen .watch-player__dplayer .dplayer-comment-box,.watch-container.watch-container--fullscreen .watch-player__dplayer .dplayer-comment-setting-box{left:20px!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container.watch-container--fullscreen .watch-player__dplayer .dplayer-comment-box,.watch-container.watch-container--fullscreen .watch-player__dplayer .dplayer-comment-setting-box{left:16px!important}}@media(max-width:1000px)and (max-height:450px){.watch-container.watch-container--fullscreen .watch-player__dplayer .dplayer-comment-box,.watch-container.watch-container--fullscreen .watch-player__dplayer .dplayer-comment-setting-box{left:16px!important}}@media(max-width:600px)and (min-height:450.01px){.watch-container.watch-container--fullscreen .watch-player__dplayer .dplayer-comment-box,.watch-container.watch-container--fullscreen .watch-player__dplayer .dplayer-comment-setting-box{left:16px!important}}.watch-container.watch-container--fullscreen .watch-header__back-icon{display:none!important}.watch-container.watch-container--fullscreen.watch-container--control-display .watch-player__dplayer .dplayer-info-panel,.watch-container.watch-container--fullscreen.watch-container--control-display .watch-player__dplayer .dplayer-notice{left:30px!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container.watch-container--fullscreen.watch-container--control-display .watch-player__dplayer .dplayer-info-panel,.watch-container.watch-container--fullscreen.watch-container--control-display .watch-player__dplayer .dplayer-notice{left:16px!important}}@media(max-width:1000px)and (max-height:450px){.watch-container.watch-container--fullscreen.watch-container--control-display .watch-player__dplayer .dplayer-info-panel,.watch-container.watch-container--fullscreen.watch-container--control-display .watch-player__dplayer .dplayer-notice{left:16px!important}}@media(max-width:600px)and (min-height:450.01px){.watch-container.watch-container--fullscreen.watch-container--control-display .watch-player__dplayer .dplayer-info-panel,.watch-container.watch-container--fullscreen.watch-container--control-display .watch-player__dplayer .dplayer-notice{left:16px!important}}.watch-player.watch-player--virtual-keyboard-display .watch-player__dplayer .dplayer-controller-mask{position:absolute;bottom:env(keyboard-inset-height,0)!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-player.watch-player--virtual-keyboard-display .watch-player__dplayer .dplayer-controller-mask{bottom:0!important}}@media(max-width:600px)and (min-height:450.01px){.watch-player.watch-player--virtual-keyboard-display .watch-player__dplayer .dplayer-controller-mask{bottom:0!important}}.watch-player.watch-player--virtual-keyboard-display .watch-player__dplayer .dplayer-icons.dplayer-comment-box{position:absolute;bottom:calc(env(keyboard-inset-height, 0px) + 4px)!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-player.watch-player--virtual-keyboard-display .watch-player__dplayer .dplayer-icons.dplayer-comment-box{bottom:6px!important}}@media(max-width:600px)and (min-height:450.01px){.watch-player.watch-player--virtual-keyboard-display .watch-player__dplayer .dplayer-icons.dplayer-comment-box{bottom:6px!important}}.shortcut-key[data-v-7aded832]{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;min-width:32px;min-height:28px;padding:3px 8px;border-radius:5px;background-color:var(--v-background-lighten2);font-size:14.5px;text-align:center}.shortcut-key-plus[data-v-7aded832]{display:inline-block;margin:0 5px;flex-shrink:0}.route-container[data-v-7aded832]{height:100vh!important;height:100dvh!important;background:var(--v-black-base)!important;overflow:hidden}@supports(-webkit-touch-callout:none){.route-container[data-v-7aded832]{height:-webkit-fill-available!important}}.watch-container[data-v-7aded832]{display:flex;width:calc(100% + 352px);height:100%;transition:width .4s cubic-bezier(.26,.68,.55,.99)}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container[data-v-7aded832]{flex-direction:column;width:100%}}@media(max-width:1000px)and (max-height:450px){.watch-container[data-v-7aded832]{width:calc(100% + 310px)}}@media(max-width:600px)and (min-height:450.01px){.watch-container[data-v-7aded832]{flex-direction:column;width:100%;padding-bottom:56px}}.watch-container.watch-container--control-display .watch-content[data-v-7aded832]{cursor:auto!important}.watch-container.watch-container--control-display .watch-header[data-v-7aded832],.watch-container.watch-container--control-display .watch-navigation[data-v-7aded832],.watch-container.watch-container--control-display .watch-player__button[data-v-7aded832]{opacity:1!important;visibility:visible!important}.watch-container.watch-container--panel-display[data-v-7aded832]{width:100%}.watch-container.watch-container--panel-display .switch-button-panel .switch-button-icon[data-v-7aded832]{color:var(--v-primary-base)}@media(hover:none){.watch-container.watch-container--panel-display .watch-panel[data-v-7aded832]{content-visibility:auto}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container[data-v-7aded832]{width:100%}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px)and (hover:none){.watch-container .watch-panel[data-v-7aded832]{content-visibility:auto}}@media(max-width:600px)and (min-height:450.01px){.watch-container[data-v-7aded832]{width:100%}}@media(max-width:600px)and (min-height:450.01px)and (hover:none){.watch-container .watch-panel[data-v-7aded832]{content-visibility:auto}}.watch-container.watch-container--fullscreen .watch-navigation[data-v-7aded832]{display:none}.watch-container.watch-container--fullscreen .watch-content .watch-header[data-v-7aded832]{padding-left:30px}@media(max-width:1000px)and (max-height:450px){.watch-container.watch-container--fullscreen .watch-content .watch-header[data-v-7aded832]{padding-left:16px}}.watch-container .watch-navigation[data-v-7aded832]{display:flex;flex-direction:column;position:fixed;width:68px;top:0;left:0;bottom:-100px;padding:18px 8px 122px;background:#2f221f80;transition:opacity .3s,visibility .3s;opacity:0;visibility:hidden;z-index:2}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-navigation[data-v-7aded832]{display:none}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-navigation[data-v-7aded832]{display:none}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-navigation[data-v-7aded832]{display:none}}.watch-container .watch-navigation .watch-navigation__icon[data-v-7aded832]{display:flex;justify-content:center;align-items:center;height:52px;margin-bottom:17px;border-radius:11px;font-size:16px;color:var(--v-text-base);transition:background-color .15s;text-decoration:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-navigation .watch-navigation__icon[data-v-7aded832]{height:32px;border-radius:10px}.watch-container .watch-navigation div.spacer[data-v-7aded832]{display:none}}.watch-container .watch-navigation .watch-navigation__link[data-v-7aded832]{display:flex;justify-content:center;align-items:center;height:52px;border-radius:11px;font-size:16px;color:var(--v-text-base);transition:background-color .15s;text-decoration:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-navigation .watch-navigation__link[data-v-7aded832]{height:44px;border-radius:10px}.watch-container .watch-navigation .watch-navigation__link[data-v-7aded832]:last-child,.watch-container .watch-navigation .watch-navigation__link[data-v-7aded832]:nth-last-child(2){display:none}}.watch-container .watch-navigation .watch-navigation__link[data-v-7aded832]:hover{background:#433532a0}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-navigation .watch-navigation__link-icon[data-v-7aded832]{width:26px;height:26px}}.watch-container .watch-navigation .watch-navigation__link--active[data-v-7aded832]{color:var(--v-primary-base);background:#433532a0}.watch-container .watch-navigation .watch-navigation__link+.watch-navigation__link[data-v-7aded832]{margin-top:4px}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-navigation .watch-navigation__link+.watch-navigation__link[data-v-7aded832]{margin-top:auto}}.watch-container .watch-content[data-v-7aded832]{display:flex;position:relative;width:100%;cursor:none}.watch-container .watch-content .watch-header[data-v-7aded832]{display:flex;align-items:center;position:absolute;top:0;left:0;width:100%;height:82px;padding-left:98px;padding-right:30px;background:linear-gradient(180deg,#000000cf,transparent);transition:opacity .3s,visibility .3s;opacity:0;visibility:hidden;z-index:1}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-content .watch-header[data-v-7aded832]{height:66px;padding-left:16px;padding-right:16px}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-header[data-v-7aded832]{padding-left:84px;padding-right:16px;height:66px;padding-left:16px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-header[data-v-7aded832]{display:none;height:50px;padding-left:16px;padding-right:16px}}.watch-container .watch-content .watch-header .watch-header__back-icon[data-v-7aded832]{display:none}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-content .watch-header .watch-header__back-icon[data-v-7aded832]{display:flex;position:relative!important;align-items:center;justify-content:center;flex-shrink:0;width:40px;height:40px;left:-6px;padding:6px;margin-right:2px;border-radius:50%;color:var(--v-text-base)}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-header .watch-header__back-icon[data-v-7aded832]{display:flex;position:relative!important;align-items:center;justify-content:center;flex-shrink:0;width:36px;height:36px;left:-6px;padding:6px;margin-right:2px;border-radius:50%;color:var(--v-text-base)}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-header .watch-header__back-icon[data-v-7aded832]{display:flex;position:relative!important;align-items:center;justify-content:center;flex-shrink:0;width:36px;height:36px;left:-6px;padding:6px;margin-right:2px;border-radius:50%;color:var(--v-text-base)}}.watch-container .watch-content .watch-header .watch-header__broadcaster[data-v-7aded832]{display:inline-block;flex-shrink:0;width:64px;height:36px;border-radius:5px;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2));-o-object-fit:cover;object-fit:cover;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-content .watch-header .watch-header__broadcaster[data-v-7aded832]{width:48px;height:28px;border-radius:4px}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-header .watch-header__broadcaster[data-v-7aded832]{width:48px;height:28px;border-radius:4px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-header .watch-header__broadcaster[data-v-7aded832]{display:none}}.watch-container .watch-content .watch-header .watch-header__program-title[data-v-7aded832]{margin-left:18px;font-size:18px;font-weight:700;font-feature-settings:"palt" 1;letter-spacing:.05em;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-content .watch-header .watch-header__program-title[data-v-7aded832]{margin-left:12px;font-size:16px}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-header .watch-header__program-title[data-v-7aded832]{margin-left:12px;font-size:16px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-header .watch-header__program-title[data-v-7aded832]{margin-left:0;font-size:16px}}.watch-container .watch-content .watch-header .watch-header__program-time[data-v-7aded832]{flex-shrink:0;margin-left:16px;font-size:15px;font-weight:600}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-header .watch-header__program-time[data-v-7aded832]{margin-left:8px;font-size:14px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-header .watch-header__program-time[data-v-7aded832]{margin-left:8px;font-size:14px}}.watch-container .watch-content .watch-header .watch-header__now[data-v-7aded832]{flex-shrink:0;margin-left:16px;font-size:13px;font-weight:600}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-header .watch-header__now[data-v-7aded832]{display:none}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-header .watch-header__now[data-v-7aded832]{display:none}}.watch-container .watch-content .watch-player[data-v-7aded832]{display:flex;position:relative;width:100%;height:100%;background-size:contain;background-position:50%}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-content .watch-player[data-v-7aded832]{aspect-ratio:16/9}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-player[data-v-7aded832]{aspect-ratio:16/9}}.watch-container .watch-content .watch-player .watch-player__background-wrapper[data-v-7aded832]{position:absolute;top:0;left:0;width:100%;height:100%}.watch-container .watch-content .watch-player .watch-player__background-wrapper .watch-player__background[data-v-7aded832]{position:relative;top:50%;left:50%;max-height:100%;aspect-ratio:16/9;transform:translate(-50%,-50%);background-blend-mode:overlay;background-color:rgba(14,14,18,.35);background-size:cover;background-image:none;opacity:0;visibility:hidden;transition:opacity .4s cubic-bezier(.4,.38,.49,.94),visibility .4s cubic-bezier(.4,.38,.49,.94);will-change:opacity}.watch-container .watch-content .watch-player .watch-player__background-wrapper .watch-player__background--display[data-v-7aded832]{opacity:1;visibility:visible}.watch-container .watch-content .watch-player .watch-player__background-wrapper .watch-player__background .watch-player__background-logo[data-v-7aded832]{display:inline-block;position:absolute;height:34px;right:56px;bottom:44px;filter:drop-shadow(0 0 5px var(--v-black-base))}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-content .watch-player .watch-player__background-wrapper .watch-player__background .watch-player__background-logo[data-v-7aded832]{height:30px;right:34px;bottom:30px}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-player .watch-player__background-wrapper .watch-player__background .watch-player__background-logo[data-v-7aded832]{height:25px;right:30px;bottom:24px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-player .watch-player__background-wrapper .watch-player__background .watch-player__background-logo[data-v-7aded832]{height:22px;right:30px;bottom:24px}}.watch-container .watch-content .watch-player .watch-player__buffering[data-v-7aded832]{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--v-background-lighten3);filter:drop-shadow(0 0 3px rgba(0,0,0,.3));opacity:0;visibility:hidden;transition:opacity .2s cubic-bezier(.4,.38,.49,.94),visibility .2s cubic-bezier(.4,.38,.49,.94);will-change:opacity;z-index:3}.watch-container .watch-content .watch-player .watch-player__buffering--display[data-v-7aded832]{opacity:1;visibility:visible}.watch-container .watch-content .watch-player .watch-player__dplayer[data-v-7aded832]{width:100%}.watch-container .watch-content .watch-player .watch-player__button[data-v-7aded832]{display:flex;justify-content:space-around;flex-direction:column;position:absolute;top:50%;right:28px;height:190px;transform:translateY(-50%);opacity:0;visibility:hidden;transition:opacity .3s,visibility .3s}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-content .watch-player .watch-player__button[data-v-7aded832]{right:15px;height:128px}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-player .watch-player__button[data-v-7aded832]{right:15px;height:155px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-player .watch-player__button[data-v-7aded832]{right:15px;height:100px}}.watch-container .watch-content .watch-player .watch-player__button .switch-button[data-v-7aded832]{display:flex;justify-content:center;align-items:center;width:48px;height:48px;color:var(--v-text-base);background:#2f221fc0;border-radius:7px;transition:background-color .15s;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-player .watch-player__button .switch-button[data-v-7aded832]{width:38px;height:38px;border-radius:5px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-player .watch-player__button .switch-button[data-v-7aded832]{width:38px;height:38px;border-radius:5px}}.watch-container .watch-content .watch-player .watch-player__button .switch-button[data-v-7aded832]:hover{background:#2f221ff0}@media(hover:none){.watch-container .watch-content .watch-player .watch-player__button .switch-button[data-v-7aded832]:hover{background:#2f221fc0}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-player .watch-player__button .switch-button svg[data-v-7aded832]{height:27px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-player .watch-player__button .switch-button svg[data-v-7aded832]{height:27px}}.watch-container .watch-content .watch-player .watch-player__button .switch-button .switch-button-icon[data-v-7aded832]{position:relative}.watch-container .watch-content .watch-player .watch-player__button .switch-button-up>.switch-button-icon[data-v-7aded832]{top:6px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-content .watch-player .watch-player__button .switch-button-panel[data-v-7aded832]{display:none}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-player .watch-player__button .switch-button-panel[data-v-7aded832]{display:none}}.watch-container .watch-content .watch-player .watch-player__button .switch-button-panel>.switch-button-icon[data-v-7aded832]{top:1.5px;transition:color .4s cubic-bezier(.26,.68,.55,.99)}.watch-container .watch-content .watch-player .watch-player__button .switch-button-down>.switch-button-icon[data-v-7aded832]{bottom:4px}.watch-container .watch-panel[data-v-7aded832]{display:flex;flex-direction:column;flex-shrink:0;width:352px;height:100%;background:var(--v-background-base)}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-panel[data-v-7aded832]{width:100%;height:auto;flex-grow:1}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-panel[data-v-7aded832]{width:310px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-panel[data-v-7aded832]{width:100%;height:auto;flex-grow:1}}@media(hover:none){.watch-container .watch-panel[data-v-7aded832]{content-visibility:hidden}}.watch-container .watch-panel .watch-panel__header[data-v-7aded832]{display:flex;align-items:center;flex-shrink:0;width:100%;height:70px;padding-left:16px;padding-right:16px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-panel .watch-panel__header[data-v-7aded832]{display:none}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-panel .watch-panel__header[data-v-7aded832]{display:none}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-panel .watch-panel__header[data-v-7aded832]{display:none}}.watch-container .watch-panel .watch-panel__header .panel-close-button[data-v-7aded832]{display:flex;position:relative;align-items:center;flex-shrink:0;left:-4px;height:35px;padding:0 4px;border-radius:5px;font-size:16px;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer}.watch-container .watch-panel .watch-panel__header .panel-close-button__icon[data-v-7aded832]{position:relative;left:-4px}.watch-container .watch-panel .watch-panel__header .panel-close-button__text[data-v-7aded832]{font-weight:700}.watch-container .watch-panel .watch-panel__header .panel-broadcaster[data-v-7aded832]{display:flex;align-items:center;min-width:0;margin-left:16px}.watch-container .watch-panel .watch-panel__header .panel-broadcaster__icon[data-v-7aded832]{display:inline-block;flex-shrink:0;width:43px;height:24px;border-radius:3px;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2));-o-object-fit:cover;object-fit:cover;-webkit-user-select:none;-moz-user-select:none;user-select:none}.watch-container .watch-panel .watch-panel__header .panel-broadcaster__number[data-v-7aded832]{flex-shrink:0;margin-left:8px;font-size:16px}.watch-container .watch-panel .watch-panel__header .panel-broadcaster__name[data-v-7aded832]{margin-left:5px;font-size:16px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-panel .watch-panel__header .panel-broadcaster__name[data-v-7aded832]{font-size:14px}}.watch-container .watch-panel .watch-panel__content-container[data-v-7aded832]{position:relative;height:100%}.watch-container .watch-panel .watch-panel__content-container .watch-panel__content[data-v-7aded832]{position:absolute;top:0;left:0;right:0;bottom:0;background:var(--v-background-base);transition:opacity .2s,visibility .2s;opacity:0;visibility:hidden}@media(hover:none){.watch-container .watch-panel .watch-panel__content-container .watch-panel__content[data-v-7aded832]{transition:none;content-visibility:hidden}}.watch-container .watch-panel .watch-panel__content-container .watch-panel__content--active[data-v-7aded832]{opacity:1;visibility:visible;content-visibility:auto}.watch-container .watch-panel .watch-panel__navigation[data-v-7aded832]{display:flex;align-items:center;justify-content:space-evenly;flex-shrink:0;height:77px;background:var(--v-background-lighten1)}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-panel .watch-panel__navigation[data-v-7aded832]{height:48px;background:var(--v-background-base)}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-panel .watch-panel__navigation[data-v-7aded832]{height:34px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-panel .watch-panel__navigation[data-v-7aded832]{height:44px;background:var(--v-background-base)}}.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button[data-v-7aded832]{display:flex;justify-content:center;align-items:center;flex-direction:column;width:77px;height:56px;padding:6px 0;border-radius:5px;color:var(--v-text-base);box-sizing:content-box;transition:color .3s;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button[data-v-7aded832]{width:100px;height:40px;padding:5px 0;box-sizing:border-box}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button[data-v-7aded832]{height:34px;padding:5px 0;box-sizing:border-box}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button[data-v-7aded832]{height:34px;padding:5px 0;box-sizing:border-box}}.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button--active .panel-navigation-button__icon[data-v-7aded832],.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button--active[data-v-7aded832]{color:var(--v-primary-base)}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button--active[data-v-7aded832]{background:#5b2d3c}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button--active[data-v-7aded832]{background:#5b2d3c}}.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button__icon[data-v-7aded832]{height:34px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button__icon[data-v-7aded832]{color:var(--v-text-base)}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button__icon[data-v-7aded832]{color:var(--v-text-base)}}.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button__text[data-v-7aded832]{margin-top:5px;font-size:13px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button__text[data-v-7aded832]{display:none}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button__text[data-v-7aded832]{display:none}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button__text[data-v-7aded832]{display:none}} \ No newline at end of file +@font-face{font-family:Noto Sans JP Caption;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/noto-sans-japanese@1.0.0/fonts/NotoSansJP-Medium.woff2) format("woff2")}@font-face{font-family:"Open Sans (for iOS Safari)";font-style:normal;font-weight:400;font-stretch:100%;src:url(https://fonts.gstatic.com/s/opensans/v34/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4uaVIGxA.woff2) format("woff2");unicode-range:u+0100-024f,u+0259,u+1e??,u+2020,u+20a0-20ab,u+20ad-20cf,u+2113,u+2c60-2c7f,u+a720-a7ff}@font-face{font-family:"Open Sans (for iOS Safari)";font-style:normal;font-weight:400;font-stretch:100%;src:url(https://fonts.gstatic.com/s/opensans/v34/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVI.woff2) format("woff2");unicode-range:u+00??,u+0131,u+0152-0153,u+02bb-02bc,u+02c6,u+02da,u+02dc,u+2000-206f,u+2074,u+20ac,u+2122,u+2191,u+2193,u+2212,u+2215,u+feff,u+fffd}@font-face{font-family:"Open Sans (for iOS Safari)";font-style:normal;font-weight:600;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/opensans/v34/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format("woff2");unicode-range:u+0100-024f,u+0259,u+1e??,u+2020,u+20a0-20ab,u+20ad-20cf,u+2113,u+2c60-2c7f,u+a720-a7ff}@font-face{font-family:"Open Sans (for iOS Safari)";font-style:normal;font-weight:600;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/opensans/v34/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format("woff2");unicode-range:u+00??,u+0131,u+0152-0153,u+02bb-02bc,u+02c6,u+02da,u+02dc,u+2000-206f,u+2074,u+20ac,u+2122,u+2191,u+2193,u+2212,u+2215,u+feff,u+fffd}@font-face{font-family:"Open Sans (for iOS Safari)";font-style:normal;font-weight:700;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/opensans/v34/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format("woff2");unicode-range:u+0100-024f,u+0259,u+1e??,u+2020,u+20a0-20ab,u+20ad-20cf,u+2113,u+2c60-2c7f,u+a720-a7ff}@font-face{font-family:"Open Sans (for iOS Safari)";font-style:normal;font-weight:700;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/opensans/v34/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format("woff2");unicode-range:u+00??,u+0131,u+0152-0153,u+02bb-02bc,u+02c6,u+02da,u+02dc,u+2000-206f,u+2074,u+20ac,u+2122,u+2191,u+2193,u+2212,u+2215,u+feff,u+fffd}html{overflow-y:auto!important;touch-action:manipulation}body .v-application{min-height:100vh;min-height:100dvh;font-family:YakuHanJPs,Open Sans,Hiragino Sans,Noto Sans JP,sans-serif;font-weight:500;overflow-x:clip;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@supports(-webkit-touch-callout:none){body .v-application{min-height:-webkit-fill-available;font-family:YakuHanJPs,"Open Sans (for iOS Safari)",Hiragino Sans,Noto Sans JP,sans-serif;font-weight:400}}body .v-application .v-application--wrap{min-height:100%!important}body main{display:flex;width:100%;min-height:100%}body header+main{padding-top:65px!important}@media(max-width:1000px)and (max-height:450px){body header+main{padding-top:0!important}}@media(max-width:600px)and (min-height:450.01px){body header+main{padding-top:0!important;padding-bottom:calc(env(safe-area-inset-bottom) + 56px)!important}}body .route-container{height:100%;background:var(--v-background-base)}.v-btn{letter-spacing:0!important;text-transform:none!important}@media(max-width:600px)and (min-height:450.01px){.v-snack{padding-bottom:56px!important}}.v-snack .v-btn__content{color:var(--v-primary-lighten1);letter-spacing:.3}.v-snack .error .v-btn__content,.v-snack .info .v-btn__content,.v-snack .success .v-btn__content,.v-snack .warning .v-btn__content{color:var(--v-text-base);letter-spacing:.3}.v-snack:not(.v-snack--absolute){height:100dvh!important}@media(max-width:600px)and (min-height:450.01px){.v-dialog{margin:24px 6px!important}.v-dialog--fullscreen{margin:0!important}}.v-popper--theme-tooltip .v-popper__inner{display:inline-block;padding:4px 10px;border-radius:4px;background:var(--v-background-lighten1);color:var(--v-text-base);font-size:12px;font-family:YakuHanJPs,Open Sans,Hiragino Sans,Noto Sans JP,sans-serif;font-weight:500;opacity:.9;line-height:22px}.v-popper--theme-tooltip .v-popper__arrow-container{display:none}@media(hover:none){:hover:before{background-color:transparent!important}}::-moz-selection{background-color:#e64f9780}::selection{background-color:#e64f9780}.decorate-symbol{display:inline-flex;justify-content:center;align-items:center;position:relative;padding:0 3px;margin-left:2.5px;margin-right:2.5px;border-radius:4px;color:var(--v-text-base);background:var(--v-primary-base);font-size:.94em}*{scrollbar-color:var(--v-gray-base) var(--v-background-base);scrollbar-width:thin}::-webkit-scrollbar{width:7px;height:7px}::-webkit-scrollbar-track{background:var(--v-background-base)}::-webkit-scrollbar-thumb{background:var(--v-background-lighten2)}::-webkit-scrollbar-thumb:hover{background:var(--v-gray-base)}@media(max-width:600px)and (min-height:450.01px){.v-menu__content .v-list{background-color:var(--v-background-lighten1)!important}}.v-menu__content::-webkit-scrollbar{width:12px;height:12px}.v-menu__content::-webkit-scrollbar-thumb{border:solid 3.5px var(--v-background-base)}.v-card__text{font-weight:inherit!important}.v-enter-active,.v-leave-active{transition:opacity .25s}.v-enter,.v-leave-to{opacity:0}.v-enter-active.route-container{position:fixed;top:0;left:0;right:0}.cursor-pointer{cursor:pointer}.w-25{width:25%}.w-50{width:50%}.w-75{width:75%}.w-100{width:100%}.h-25{height:25%}.h-50{height:50%}.h-75{height:75%}.h-100{height:100%}.header[data-v-84897154]{position:fixed;display:flex;align-items:center;width:100%;height:65px;padding:4px 16px;background:var(--v-background-base);box-shadow:0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12);z-index:10}@media(max-width:1000px)and (max-height:450px){.header[data-v-84897154]{width:210px;height:48px;justify-content:center}}@media(max-width:680px)and (max-height:450px){.header[data-v-84897154]{width:190px}}@media(max-width:600px)and (min-height:450.01px){.header[data-v-84897154]{display:none}}@media(max-width:1000px)and (max-height:450px){.header .spacer[data-v-84897154]{display:none}}.header .konomitv-logo[data-v-84897154]{display:block;padding:12px 8px;border-radius:8px}@media(max-width:1000px)and (max-height:450px){.header .konomitv-logo[data-v-84897154]{margin:0!important}}.header .konomitv-logo__image[data-v-84897154]{display:block}@media(max-width:1000px)and (max-height:450px){.header .konomitv-logo__image[data-v-84897154]{height:19.5px}}.bottom-navigation-container[data-v-3df53df3]{display:none;position:fixed;bottom:0;z-index:8}@media(max-width:600px)and (min-height:450.01px){.bottom-navigation-container[data-v-3df53df3]{display:flex;padding-bottom:env(safe-area-inset-bottom);box-sizing:content-box}}.bottom-navigation-container .v-btn.bottom-navigation-button[data-v-3df53df3]{color:var(--v-text-darken1)!important;font-weight:700}.bottom-navigation-container .v-btn.bottom-navigation-button.v-btn--active[data-v-3df53df3]{color:var(--v-primary-base)!important}.navigation-container[data-v-5b40940b]{flex-shrink:0;width:220px;background:var(--v-background-lighten1)}@media(max-width:1000px)and (max-height:450px){.navigation-container[data-v-5b40940b]{width:210px}}@media(max-width:680px)and (max-height:450px){.navigation-container[data-v-5b40940b]{width:190px}}@media(max-width:600px)and (min-height:450.01px){.navigation-container[data-v-5b40940b]{display:none}}.navigation-container .navigation[data-v-5b40940b]{position:fixed;width:220px;top:65px;left:0;bottom:-100px;padding-bottom:100px;background:var(--v-background-lighten1);z-index:1}@media(max-width:1000px)and (max-height:450px){.navigation-container .navigation[data-v-5b40940b]{top:48px;width:210px}}@media(max-width:680px)and (max-height:450px){.navigation-container .navigation[data-v-5b40940b]{width:190px}}.navigation-container .navigation .navigation-scroll[data-v-5b40940b]{display:flex;flex-direction:column;height:100%;padding:22px 12px;overflow-y:auto}@media(max-width:1000px)and (max-height:450px){.navigation-container .navigation .navigation-scroll[data-v-5b40940b]{padding:10px 12px}}@media(max-width:680px)and (max-height:450px){.navigation-container .navigation .navigation-scroll[data-v-5b40940b]{padding:10px 8px}}.navigation-container .navigation .navigation-scroll[data-v-5b40940b]::-webkit-scrollbar-track{background:var(--v-background-lighten1)}.navigation-container .navigation .navigation-scroll .navigation__link[data-v-5b40940b]{display:flex;align-items:center;flex-shrink:0;height:52px;padding-left:16px;margin-top:4px;border-radius:11px;font-size:16px;color:var(--v-text-base);transition:background-color .15s;text-decoration:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(max-width:1000px)and (max-height:450px){.navigation-container .navigation .navigation-scroll .navigation__link[data-v-5b40940b]{height:40px;padding-left:12px;border-radius:9px;font-size:15px}}.navigation-container .navigation .navigation-scroll .navigation__link[data-v-5b40940b]:hover{background:var(--v-background-lighten2)}.navigation-container .navigation .navigation-scroll .navigation__link[data-v-5b40940b]:first-of-type{margin-top:0}.navigation-container .navigation .navigation-scroll .navigation__link--active[data-v-5b40940b]{color:var(--v-primary-base);background:#5b2d3c}.navigation-container .navigation .navigation-scroll .navigation__link--active[data-v-5b40940b]:hover{background:#5b2d3c}.navigation-container .navigation .navigation-scroll .navigation__link--highlight[data-v-5b40940b]{color:var(--v-secondary-lighten1)}.navigation-container .navigation .navigation-scroll .navigation__link--version[data-v-5b40940b]{font-size:15px}@media(max-width:1000px)and (max-height:450px){.navigation-container .navigation .navigation-scroll .navigation__link--version[data-v-5b40940b]{font-size:14.5px}}.navigation-container .navigation .navigation-scroll .navigation__link .navigation__link-icon[data-v-5b40940b]{margin-right:14px}@media(max-width:1000px)and (max-height:450px){.navigation-container .navigation .navigation-scroll .navigation__link .navigation__link-icon[data-v-5b40940b]{margin-right:10px}.login-container-wrapper[data-v-851c3dec]{padding:20px!important;margin-bottom:0!important}}@media(max-width:600px)and (min-height:450.01px){.login-container-wrapper[data-v-851c3dec]{margin-bottom:0!important}}.login-container-wrapper .login-container[data-v-851c3dec]{border-radius:11px}@media(max-width:1000px)and (max-height:450px){.login-container-wrapper .login-container[data-v-851c3dec]{padding:24px!important}}@media(max-width:600px)and (min-height:450.01px){.login-container-wrapper .login-container[data-v-851c3dec]{padding:32px 20px!important;margin-left:12px!important;margin-right:12px!important}}@media(max-width:1000px)and (max-height:450px){.login-container-wrapper .login-container .login__logo[data-v-851c3dec]{padding-top:4px!important;padding-bottom:8px!important}.login-container-wrapper .login-container .login__logo .v-image[data-v-851c3dec]{max-width:200px!important}.login-container-wrapper .login-container .login__logo h4[data-v-851c3dec]{margin-top:16px!important;font-size:19px!important}}@media(max-width:600px)and (min-height:450.01px){.login-container-wrapper .login-container .login__logo[data-v-851c3dec]{padding-top:4px!important;padding-bottom:12px!important}.login-container-wrapper .login-container .login__logo .v-image[data-v-851c3dec]{max-width:200px!important}.login-container-wrapper .login-container .login__logo h4[data-v-851c3dec]{margin-top:24px!important;font-size:19px!important}}@media(max-width:1000px)and (max-height:450px){.login-container-wrapper .login-container .v-input[data-v-851c3dec]{margin-top:24px!important;font-size:14px!important}}@media(max-width:600px)and (min-height:450.01px){.login-container-wrapper .login-container .v-input[data-v-851c3dec]{margin-top:32px!important;font-size:16px!important}}.login-container-wrapper .login-container .login-button[data-v-851c3dec]{border-radius:7px;margin-top:48px!important;font-size:18px;letter-spacing:0}@media(max-width:1000px)and (max-height:450px){.login-container-wrapper .login-container .login-button[data-v-851c3dec]{height:44px!important;margin-top:24px!important;font-size:16px}}@media(max-width:600px)and (min-height:450.01px){.login-container-wrapper .login-container .login-button[data-v-851c3dec]{height:50px!important;margin-top:32px!important;font-size:15.5px}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){h1[data-v-1310cfee]{font-size:24px!important}}@media(max-width:1000px)and (max-height:450px){h1[data-v-1310cfee]{font-size:24px!important}}@media(max-width:600px)and (min-height:450.01px){h1[data-v-1310cfee]{font-size:24px!important;text-align:center}}@media(max-width:1000px)and (max-height:450px){span[data-v-1310cfee]{font-size:15px!important}}@media(max-width:600px)and (min-height:450.01px){span[data-v-1310cfee]{font-size:14px!important}}@media(max-width:1000px)and (max-height:450px){.register-container-wrapper[data-v-6533f3d0]{padding:20px!important;margin-bottom:0!important}}@media(max-width:600px)and (min-height:450.01px){.register-container-wrapper[data-v-6533f3d0]{margin-bottom:0!important}}.register-container-wrapper .register-container[data-v-6533f3d0]{border-radius:11px}@media(max-width:1000px)and (max-height:450px){.register-container-wrapper .register-container[data-v-6533f3d0]{padding:24px!important}}@media(max-width:600px)and (min-height:450.01px){.register-container-wrapper .register-container[data-v-6533f3d0]{padding:32px 20px!important;margin-left:12px!important;margin-right:12px!important}}@media(max-width:1000px)and (max-height:450px){.register-container-wrapper .register-container .register__logo[data-v-6533f3d0]{padding-top:4px!important;padding-bottom:8px!important}.register-container-wrapper .register-container .register__logo .v-image[data-v-6533f3d0]{max-width:200px!important}.register-container-wrapper .register-container .register__logo h4[data-v-6533f3d0]{margin-top:16px!important;font-size:19px!important}}@media(max-width:600px)and (min-height:450.01px){.register-container-wrapper .register-container .register__logo[data-v-6533f3d0]{padding-top:4px!important;padding-bottom:12px!important}.register-container-wrapper .register-container .register__logo .v-image[data-v-6533f3d0]{max-width:200px!important}.register-container-wrapper .register-container .register__logo h4[data-v-6533f3d0]{margin-top:24px!important;font-size:19px!important}}@media(max-width:1000px)and (max-height:450px){.register-container-wrapper .register-container .v-input[data-v-6533f3d0]{margin-top:0!important;font-size:14px!important}}@media(max-width:600px)and (min-height:450.01px){.register-container-wrapper .register-container .v-input[data-v-6533f3d0]{margin-top:2px!important;font-size:16px!important}}@media(max-width:1000px)and (max-height:450px){.register-container-wrapper .register-container .v-input[data-v-6533f3d0]:first-child{margin-top:24px!important}}@media(max-width:600px)and (min-height:450.01px){.register-container-wrapper .register-container .v-input[data-v-6533f3d0]:first-child{margin-top:32px!important}}.register-container-wrapper .register-container .register-button[data-v-6533f3d0]{border-radius:7px;margin-top:18px!important;font-size:18px;letter-spacing:0}@media(max-width:1000px)and (max-height:450px){.register-container-wrapper .register-container .register-button[data-v-6533f3d0]{height:44px!important;margin-top:0!important;font-size:16px}}@media(max-width:600px)and (min-height:450.01px){.register-container-wrapper .register-container .register-button[data-v-6533f3d0]{height:50px!important;margin-top:2px!important;font-size:15.5px}}.settings-container[data-v-d0f5a998]{width:100%;min-width:0}@media(max-width:1000px)and (max-height:450px){.settings-container[data-v-d0f5a998]{padding:16px 20px!important}}@media(max-width:680px)and (max-height:450px){.settings-container[data-v-d0f5a998]{padding:16px 16px!important}}@media(max-width:600px)and (min-height:450.01px){.settings-container[data-v-d0f5a998]{padding:0 0!important}}.settings-container .settings-navigation[data-v-d0f5a998]{position:sticky;top:85px!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.settings-container .settings-navigation[data-v-d0f5a998]{display:none}}@media(max-width:1000px)and (max-height:450px){.settings-container .settings-navigation[data-v-d0f5a998]{display:none}}@media(max-width:600px)and (min-height:450.01px){.settings-container .settings-navigation[data-v-d0f5a998]{display:none}}.settings-container .settings-navigation .v-list-item--link[data-v-d0f5a998],.settings-container .settings-navigation .v-list-item--link[data-v-d0f5a998]:before{border-radius:11px!important}.settings-container .settings[data-v-d0f5a998]{width:100%;min-width:0;border-radius:11px!important;background-color:var(--v-background-lighten1)}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.settings-container .settings[data-v-d0f5a998]{margin-left:0!important;padding-top:20px!important;padding-left:20px!important;padding-right:20px!important}}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998]{padding:20px!important;margin-left:0!important}}@media(max-width:600px)and (min-height:450.01px){.settings-container .settings[data-v-d0f5a998]{margin-left:0!important;padding-top:60px!important;padding-left:16px!important;padding-right:16px!important;border-radius:0!important;background-color:var(--v-background-base)}}.settings-container .settings[data-v-d0f5a998] .settings__heading{display:flex;align-items:center;font-size:22px}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__heading{font-size:20px}}@media(max-width:600px)and (min-height:450.01px){.settings-container .settings[data-v-d0f5a998] .settings__heading{position:fixed;top:0;left:0;right:0;height:60px;padding:16px;border-radius:0;background:var(--v-background-lighten1);box-shadow:0 3px 14px 2px rgba(0,0,0,.12);z-index:5}}.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button{display:none;position:relative;left:-6px;padding:6px;border-radius:50%;color:var(--v-text-base)}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button{display:flex}}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button{display:flex}}@media(max-width:600px)and (min-height:450.01px){.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button{display:flex}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button+svg{display:none}}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button+svg{display:none}}@media(max-width:600px)and (min-height:450.01px){.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button+svg{display:none}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button+svg+span{margin-left:0!important}}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button+svg+span{margin-left:0!important}}@media(max-width:600px)and (min-height:450.01px){.settings-container .settings[data-v-d0f5a998] .settings__heading .settings__back-button+svg+span{margin-left:0!important}}.settings-container .settings[data-v-d0f5a998] .settings__content{margin-top:24px}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__content{margin-top:16px}}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item{display:flex;position:relative;flex-direction:column;margin-top:24px}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item{margin-top:16px}}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item--sync-disabled .settings__item-heading{padding-right:8px}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item--sync-disabled .settings__item-heading:after{content:"デバイス間同期無効";display:flex;align-items:center;flex-shrink:0;position:relative;right:-8px;padding:2px 4px;margin-left:auto;border-radius:4px;background:var(--v-background-lighten2);font-size:11px}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item--switch{margin-right:62px}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item--switch .settings__item-heading{width:calc(100% + 62px)}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item--disabled{opacity:.5}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item-heading{display:flex;align-items:center;color:var(--v-text-base);font-size:16.5px}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item-heading{font-size:15px}}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item-label{margin-top:8px;color:var(--v-text-darken1);font-size:13.5px;line-height:1.6}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item-label{font-size:11px;line-height:1.7}}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item-form{margin-top:14px}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item-form{font-size:13.5px}}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item-switch{align-items:center;position:absolute;top:0;right:-74px;bottom:0;margin-top:0}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item p{margin-bottom:8px}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__item p:last-of-type{margin-bottom:0}.settings-container .settings[data-v-d0f5a998] .settings__content .settings__save-button{height:45px;background:var(--v-background-lighten2);font-size:15.5px;letter-spacing:0}@media(max-width:1000px)and (max-height:450px){.settings-container .settings[data-v-d0f5a998] .settings__content .settings__save-button{height:40px;padding:0 12px;font-size:14px}}.settings__content[data-v-7749b102]{opacity:1;transition:opacity .4s}.settings__content--loading[data-v-7749b102]{opacity:0}@media(max-width:600px)and (min-height:450.01px){.settings__conflict-dialog .v-btn[data-v-7749b102]{height:50px!important;text-align:left}}.settings__conflict-dialog .v-btn br.smartphone-vertical-only[data-v-7749b102]{display:none}@media(max-width:600px)and (min-height:450.01px){.settings__conflict-dialog .v-btn br.smartphone-vertical-only[data-v-7749b102]{display:inline}}.account[data-v-7749b102]{display:flex;align-items:center;height:130px;padding:18px 20px;border-radius:15px;background:var(--v-background-lighten2)}@media(min-width:1000.01px)and (max-width:1264px)and (max-height:850px){.account[data-v-7749b102]{align-items:normal;flex-direction:column;height:auto;padding:16px}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.account[data-v-7749b102]{align-items:normal;flex-direction:column;height:auto;padding:16px}}@media(max-width:1000px)and (max-height:450px){.account[data-v-7749b102]{align-items:normal;flex-direction:column;height:auto;padding:16px;border-radius:10px}}@media(max-width:600px)and (min-height:450.01px){.account[data-v-7749b102]{align-items:normal;flex-direction:column;height:auto;padding:16px 12px;border-radius:10px}}.account-wrapper[data-v-7749b102]{display:flex;align-items:center;min-width:0;height:94px}@media(max-width:1000px)and (max-height:450px){.account-wrapper[data-v-7749b102]{height:80px}}@media(max-width:600px)and (min-height:450.01px){.account-wrapper[data-v-7749b102]{height:70px}}.account__icon[data-v-7749b102]{flex-shrink:0;min-width:94px;height:100%;border-radius:50%;-o-object-fit:cover;object-fit:cover;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2));image-rendering:-webkit-optimize-contrast}@media(max-width:1000px)and (max-height:450px){.account__icon[data-v-7749b102]{min-width:80px}}@media(max-width:600px)and (min-height:450.01px){.account__icon[data-v-7749b102]{min-width:70px}}.account__info[data-v-7749b102]{display:flex;flex-direction:column;min-width:0;margin-left:20px;margin-right:12px}@media(max-width:600px)and (min-height:450.01px){.account__info[data-v-7749b102]{margin-left:12px!important;margin-right:0!important}}.account__info-name[data-v-7749b102]{display:inline-flex;align-items:center;height:33px}.account__info-name-text[data-v-7749b102]{display:inline-block;font-size:23px;color:var(--v-text-base);font-weight:700;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}@media(max-width:1000px)and (max-height:450px){.account__info-name-text[data-v-7749b102]{font-size:21px}}@media(max-width:600px)and (min-height:450.01px){.account__info-name-text[data-v-7749b102]{font-size:20px}}.account__info-admin[data-v-7749b102]{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:52px;height:28px;margin-left:10px;border-radius:5px;background:var(--v-secondary-base);font-size:14px;font-weight:500;line-height:2}@media(max-width:1000px)and (max-height:450px){.account__info-admin[data-v-7749b102]{width:45px;height:24px;border-radius:4px;font-size:11.5px}}@media(max-width:600px)and (min-height:450.01px){.account__info-admin[data-v-7749b102]{width:45px;height:24px;border-radius:4px;font-size:11.5px}}.account__info-id[data-v-7749b102]{display:inline-block;margin-top:2px;color:var(--v-text-darken1);font-size:16px}@media(max-width:1000px)and (max-height:450px){.account__info-id[data-v-7749b102]{font-size:14.5px}}@media(max-width:600px)and (min-height:450.01px){.account__info-id[data-v-7749b102]{font-size:14.5px}}.account__login[data-v-7749b102]{border-radius:7px;font-size:16px;letter-spacing:0}@media(min-width:1000.01px)and (max-width:1264px)and (max-height:850px){.account__login[data-v-7749b102]{height:50px!important;margin-top:8px;margin-right:auto;font-size:14.5px}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.account__login[data-v-7749b102]{height:42px!important;margin-top:8px;margin-right:auto;font-size:14.5px}}@media(max-width:1000px)and (max-height:450px){.account__login[data-v-7749b102]{height:42px!important;margin-top:8px;margin-right:auto;font-size:14.5px}}@media(max-width:600px)and (min-height:450.01px){.account__login[data-v-7749b102]{height:42px!important;margin-top:16px;margin-right:auto;font-size:14.5px}}.account-register[data-v-7749b102]{display:flex;flex-direction:column;margin-top:28px}.account-register__heading[data-v-7749b102]{font-size:21px;font-weight:700;text-align:center;font-feature-settings:"palt" 1;letter-spacing:.04em}@media(max-width:1000px)and (max-height:450px){.account-register__heading[data-v-7749b102]{font-size:19px}}@media(max-width:600px)and (min-height:450.01px){.account-register__heading[data-v-7749b102]{font-size:18px}}.account-register__feature[data-v-7749b102]{display:grid;grid-template-columns:1fr 1fr;grid-row-gap:18px;grid-column-gap:16px;margin-top:28px}@media(min-width:1000.01px)and (max-width:1264px)and (max-height:850px){.account-register__feature[data-v-7749b102]{grid-template-columns:1fr}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.account-register__feature[data-v-7749b102]{grid-template-columns:1fr}}@media(max-width:1000px)and (max-height:450px){.account-register__feature[data-v-7749b102]{grid-template-columns:1fr}}@media(max-width:600px)and (min-height:450.01px){.account-register__feature[data-v-7749b102]{grid-template-columns:1fr}}.account-register__feature .account-feature[data-v-7749b102]{display:flex;align-items:center}.account-register__feature .account-feature__icon[data-v-7749b102]{width:46px;height:46px;flex-shrink:0;margin-right:16px;color:var(--v-secondary-lighten1)}.account-register__feature .account-feature__info[data-v-7749b102]{display:flex;flex-direction:column}.account-register__feature .account-feature__info-heading[data-v-7749b102]{font-size:15px}.account-register__feature .account-feature__info-text[data-v-7749b102]{margin-top:3px;color:var(--v-text-darken1);font-size:12.5px;line-height:1.65}.account-register__description[data-v-7749b102]{margin-top:32px;font-size:15px;line-height:1.7;text-align:center}.account-register__description br.smartphone-vertical-only[data-v-7749b102]{display:none}@media(min-width:1000.01px)and (max-width:1264px)and (max-height:850px){.account-register__description[data-v-7749b102]{font-size:12.5px}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.account-register__description[data-v-7749b102]{font-size:10.5px}}@media(max-width:1000px)and (max-height:450px){.account-register__description[data-v-7749b102]{font-size:12.5px}}@media(max-width:680px)and (max-height:450px){.account-register__description[data-v-7749b102]{font-size:10.5px}}@media(max-width:600px)and (min-height:450.01px){.account-register__description[data-v-7749b102]{font-size:12.5px}.account-register__description br.smartphone-vertical-only[data-v-7749b102]{display:inline}}.account-register__button[data-v-7749b102]{margin-top:24px;margin-left:auto;margin-right:auto;border-radius:7px;font-size:16px;letter-spacing:0}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.account-register__button[data-v-7749b102]{height:42px!important;font-size:14.5px}}@media(max-width:1000px)and (max-height:450px){.account-register__button[data-v-7749b102]{height:42px!important;font-size:14.5px}}@media(max-width:600px)and (min-height:450.01px){.account-register__button[data-v-7749b102]{max-width:210px!important;height:42px!important;font-size:15px}}.settings-container[data-v-48d089f3]{width:100%;min-width:0}@media(max-width:1000px)and (max-height:450px){.settings-container[data-v-48d089f3]{padding:16px 20px!important}}@media(max-width:680px)and (max-height:450px){.settings-container[data-v-48d089f3]{padding:16px 16px!important}}@media(max-width:600px)and (min-height:450.01px){.settings-container[data-v-48d089f3]{padding:16px 16px!important}}.settings-container .settings-navigation[data-v-48d089f3]{transform:none!important;visibility:visible!important}.settings-container .settings-navigation .settings-navigation-version[data-v-48d089f3]{display:none}@media(max-width:600px)and (min-height:450.01px){.settings-container .settings-navigation .settings-navigation-version[data-v-48d089f3]{display:flex}}.settings-container .settings-navigation .settings-navigation-version--highlight[data-v-48d089f3]{color:var(--v-secondary-lighten1)}@media(max-width:1000px)and (max-height:450px){.settings-container .settings-navigation h1[data-v-48d089f3]{font-size:22px!important}.settings-container .settings-navigation .v-navigation-drawer__content .v-list[data-v-48d089f3]{margin-top:12px!important;padding:0!important}}.settings-container .settings-navigation .v-list-item--link[data-v-48d089f3],.settings-container .settings-navigation .v-list-item--link[data-v-48d089f3]:before{background:var(--v-background-lighten1);border-radius:6px!important;margin-top:6px!important;margin-bottom:0!important}.settings-container .settings-navigation .v-list-item--link[data-v-48d089f3]:first-of-type{margin-top:0!important}.settings-container .settings-navigation .v-list-item__icon[data-v-48d089f3]{margin:14px 0!important;margin-right:16px!important}@media(max-width:600px)and (min-height:450.01px){.comment-mute-settings .v-card__title[data-v-2cd59ba0],.comment-mute-settings>div[data-v-2cd59ba0]{padding-left:12px!important;padding-right:12px!important}}.settings__item[data-v-2cd59ba0]{display:flex;position:relative;flex-direction:column;margin-top:24px}@media(max-width:1000px)and (max-height:450px){.settings__item[data-v-2cd59ba0]{margin-top:16px}}.settings__item--switch[data-v-2cd59ba0]{margin-right:62px}.settings__item-heading[data-v-2cd59ba0]{display:flex;align-items:center;color:var(--v-text-base);font-size:16.5px}@media(max-width:1000px)and (max-height:450px){.settings__item-heading[data-v-2cd59ba0]{font-size:15px}}.settings__item-label[data-v-2cd59ba0]{margin-top:8px;color:var(--v-text-darken1);font-size:13.5px;line-height:1.6}@media(max-width:1000px)and (max-height:450px){.settings__item-label[data-v-2cd59ba0]{font-size:11px;line-height:1.7}}.settings__item-form[data-v-2cd59ba0]{margin-top:14px}@media(max-width:1000px)and (max-height:450px){.settings__item-form[data-v-2cd59ba0]{font-size:13.5px}}.settings__item-switch[data-v-2cd59ba0]{align-items:center;position:absolute;top:0;right:-74px;bottom:0;margin-top:0}.settings__item p[data-v-2cd59ba0]{margin-bottom:8px}.settings__item p[data-v-2cd59ba0]:last-of-type{margin-bottom:0}.muted-comment-items[data-v-2cd59ba0]{display:flex;flex-direction:column;margin-top:8px}.muted-comment-items .muted-comment-item[data-v-2cd59ba0]{display:flex;align-items:center;padding:6px 0;border-bottom:1px solid var(--v-background-lighten2);transition:background-color .15s ease}.muted-comment-items .muted-comment-item[data-v-2cd59ba0]:last-of-type{border-bottom:none}.muted-comment-items .muted-comment-item__input[data-v-2cd59ba0]{font-size:14px}.muted-comment-items .muted-comment-item__match-type[data-v-2cd59ba0]{max-width:150px;margin-left:12px;font-size:14px}@media(max-width:600px)and (min-height:450.01px){.muted-comment-items .muted-comment-item__match-type[data-v-2cd59ba0]{max-width:115px}}.muted-comment-items .muted-comment-item__delete-button[data-v-2cd59ba0]{display:flex;align-items:center;justify-content:center;width:40px;height:40px;margin-left:6px;border-radius:5px;outline:none;cursor:pointer}.settings__content[data-v-ac48731c]{opacity:1;transition:opacity .4s}.settings__content--loading[data-v-ac48731c]{opacity:0}.niconico-account[data-v-ac48731c]{display:flex;align-items:center;height:120px;padding:20px;border-radius:15px;background:var(--v-background-lighten2)}@media(min-width:1000.01px)and (max-width:1264px)and (max-height:850px){.niconico-account[data-v-ac48731c]{align-items:normal;flex-direction:column;height:auto;padding:16px}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.niconico-account[data-v-ac48731c]{align-items:normal;flex-direction:column;height:auto;padding:16px}.niconico-account .niconico-account-wrapper .niconico-account__info[data-v-ac48731c]{margin-left:16px!important;margin-right:0!important}.niconico-account .niconico-account-wrapper .niconico-account__info-name-text[data-v-ac48731c]{font-size:18.5px}.niconico-account .niconico-account-wrapper .niconico-account__info-description[data-v-ac48731c]{font-size:13.5px}}@media(max-width:1000px)and (max-height:450px){.niconico-account[data-v-ac48731c]{align-items:normal;flex-direction:column;height:auto;padding:16px;border-radius:10px}.niconico-account .niconico-account-wrapper .niconico-account__info[data-v-ac48731c]{margin-right:0!important}}@media(max-width:680px)and (max-height:450px){.niconico-account .niconico-account-wrapper .niconico-account__info[data-v-ac48731c]{margin-left:16px!important}.niconico-account .niconico-account-wrapper .niconico-account__info-name-text[data-v-ac48731c]{font-size:18px}.niconico-account .niconico-account-wrapper .niconico-account__info-description[data-v-ac48731c]{font-size:13px}}@media(max-width:600px)and (min-height:450.01px){.niconico-account[data-v-ac48731c]{align-items:normal;flex-direction:column;height:auto;padding:16px 12px;border-radius:10px}.niconico-account .niconico-account-wrapper .niconico-account__info[data-v-ac48731c]{margin-left:12px!important;margin-right:0!important}.niconico-account .niconico-account-wrapper .niconico-account__info-name-text[data-v-ac48731c]{font-size:17px}.niconico-account .niconico-account-wrapper .niconico-account__info-description[data-v-ac48731c]{font-size:13px}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.niconico-account--anonymous .niconico-account__login[data-v-ac48731c]{margin-top:12px}}@media(max-width:1000px)and (max-height:450px){.niconico-account--anonymous .niconico-account__login[data-v-ac48731c]{margin-top:12px}}@media(max-width:680px)and (max-height:450px){.niconico-account--anonymous .niconico-account-wrapper svg[data-v-ac48731c]{display:none}.niconico-account--anonymous .niconico-account-wrapper .niconico-account__info[data-v-ac48731c]{margin-left:0!important}}@media(max-width:600px)and (min-height:450.01px){.niconico-account--anonymous[data-v-ac48731c]{padding-top:20px}.niconico-account--anonymous .niconico-account__login[data-v-ac48731c]{margin-top:16px}.niconico-account--anonymous .niconico-account-wrapper svg[data-v-ac48731c]{display:none}.niconico-account--anonymous .niconico-account-wrapper .niconico-account__info[data-v-ac48731c]{margin-left:0!important;margin-right:0!important}}.niconico-account-wrapper[data-v-ac48731c]{display:flex;align-items:center;min-width:0;height:80px}@media(max-width:600px)and (min-height:450.01px){.niconico-account-wrapper[data-v-ac48731c]{height:60px}}.niconico-account__icon[data-v-ac48731c]{flex-shrink:0;min-width:80px;height:100%;border-radius:50%;-o-object-fit:cover;object-fit:cover;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2));image-rendering:-webkit-optimize-contrast}@media(max-width:600px)and (min-height:450.01px){.niconico-account__icon[data-v-ac48731c]{width:60px;min-width:60px;height:60px}}.niconico-account__info[data-v-ac48731c]{display:flex;flex-direction:column;min-width:0;margin-left:20px;margin-right:16px}.niconico-account__info-name[data-v-ac48731c]{display:inline-flex;align-items:center;height:33px}@media(max-width:600px)and (min-height:450.01px){.niconico-account__info-name[data-v-ac48731c]{height:auto}}.niconico-account__info-name-text[data-v-ac48731c]{display:inline-block;font-size:20px;color:var(--v-text-base);font-weight:700;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.niconico-account__info-description[data-v-ac48731c]{display:inline-block;margin-top:4px;color:var(--v-text-darken1);font-size:14px}.niconico-account__login[data-v-ac48731c]{border-radius:7px;font-size:16px;letter-spacing:0}@media(min-width:1000.01px)and (max-width:1264px)and (max-height:850px){.niconico-account__login[data-v-ac48731c]{height:50px!important;margin-top:12px;margin-right:auto}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.niconico-account__login[data-v-ac48731c]{height:42px!important;margin-top:8px;margin-right:auto;font-size:14.5px}}@media(max-width:1000px)and (max-height:450px){.niconico-account__login[data-v-ac48731c]{height:42px!important;margin-top:8px;margin-right:auto;font-size:14.5px}}@media(max-width:600px)and (min-height:450.01px){.niconico-account__login[data-v-ac48731c]{height:42px!important;margin-top:16px;margin-right:auto;font-size:14.5px}}.settings__content[data-v-ea90430c]{opacity:1;transition:opacity .4s}.settings__content--loading[data-v-ea90430c]{opacity:0}.twitter-accounts[data-v-ea90430c]{display:flex;flex-direction:column;padding:20px 20px;border-radius:15px;background:var(--v-background-lighten2)}@media(max-width:1000px)and (max-height:450px){.twitter-accounts[data-v-ea90430c]{padding:16px 20px;border-radius:10px}}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts[data-v-ea90430c]{padding:16px 12px;border-radius:10px}}.twitter-accounts__heading[data-v-ea90430c]{display:flex;align-items:center;font-size:18px;font-weight:700}.twitter-accounts__guide[data-v-ea90430c]{display:flex;align-items:center}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-accounts__guide .text-h6[data-v-ea90430c]{font-size:19px!important}}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts__guide .text-h6[data-v-ea90430c]{font-size:17px!important}}@media(max-width:680px)and (max-height:450px){.twitter-accounts__guide svg[data-v-ea90430c]{display:none}}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts__guide svg[data-v-ea90430c]{display:none}}@media(max-width:680px)and (max-height:450px){.twitter-accounts__guide svg+div[data-v-ea90430c]{margin-left:0!important}}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts__guide svg+div[data-v-ea90430c]{margin-left:0!important}}.twitter-accounts .twitter-account[data-v-ea90430c]{display:flex;align-items:center;margin-top:20px}@media(max-width:1000px)and (max-height:450px){.twitter-accounts .twitter-account[data-v-ea90430c]{margin-top:16px}}.twitter-accounts .twitter-account__icon[data-v-ea90430c]{flex-shrink:0;width:70px;height:70px;margin-right:16px;border-radius:50%;-o-object-fit:cover;object-fit:cover;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2));image-rendering:-webkit-optimize-contrast}@media(max-width:1000px)and (max-height:450px){.twitter-accounts .twitter-account__icon[data-v-ea90430c]{width:52px;height:52px}}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts .twitter-account__icon[data-v-ea90430c]{width:48px;height:48px;margin-right:10px}}.twitter-accounts .twitter-account__info[data-v-ea90430c]{display:flex;flex-direction:column;min-width:0;margin-right:16px}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts .twitter-account__info[data-v-ea90430c]{margin-right:10px}}.twitter-accounts .twitter-account__info-name[data-v-ea90430c]{display:inline-flex;align-items:center}.twitter-accounts .twitter-account__info-name-text[data-v-ea90430c]{display:inline-block;color:var(--v-text-base);font-size:20px;font-weight:700;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}@media(max-width:1000px)and (max-height:450px){.twitter-accounts .twitter-account__info-name-text[data-v-ea90430c]{font-size:18px}}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts .twitter-account__info-name-text[data-v-ea90430c]{font-size:16px}}.twitter-accounts .twitter-account__info-screen-name[data-v-ea90430c]{display:inline-block;color:var(--v-text-darken1);font-size:16px}@media(max-width:1000px)and (max-height:450px){.twitter-accounts .twitter-account__info-screen-name[data-v-ea90430c]{font-size:14px}}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts .twitter-account__info-screen-name[data-v-ea90430c]{font-size:13.5px}}.twitter-accounts .twitter-account__login[data-v-ea90430c]{margin-top:20px;margin-left:auto;margin-right:auto;border-radius:7px;font-size:15px;letter-spacing:0}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-accounts .twitter-account__login[data-v-ea90430c]{height:42px!important;font-size:14.5px}}@media(max-width:1000px)and (max-height:450px){.twitter-accounts .twitter-account__login[data-v-ea90430c]{height:42px!important;font-size:14.5px}}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts .twitter-account__login[data-v-ea90430c]{height:42px!important;font-size:14.5px}}.twitter-accounts .twitter-account__logout[data-v-ea90430c]{background:var(--v-gray-base);border-radius:7px;font-size:15px;letter-spacing:0}@media(max-width:1000px)and (max-height:450px){.twitter-accounts .twitter-account__logout[data-v-ea90430c]{width:116px!important}}@media(max-width:600px)and (min-height:450.01px){.twitter-accounts .twitter-account__logout[data-v-ea90430c]{width:100px!important;height:48px!important;border-radius:5px;font-size:14px}.twitter-accounts .twitter-account__logout svg[data-v-ea90430c]{width:20px;margin-right:4px!important}}.channels-container.channels-container--home .v-tabs-bar{height:54px;background:linear-gradient(to bottom,var(--v-background-base) calc(100% - 3px),var(--v-background-lighten1) 3px)}@media(max-width:1000px)and (max-height:450px){.channels-container.channels-container--home .v-tabs-bar{height:46px}}.channels-container.channels-container--home .v-tabs-slider-wrapper{height:3px!important;transition:left .3s cubic-bezier(.25,.8,.5,1)}.channels-container.channels-container--home .v-window__container{min-height:calc(100vh - 180px);min-height:calc(100dvh - 180px)}@media(hover:none){.channels-container.channels-container--home .v-window__container{min-height:auto}}.channels-container.channels-container--home.channels-container--loading .v-tabs-slider-wrapper{transition:none!important}.channels-container[data-v-5395b00e]{display:flex;flex-direction:column;width:100%;margin-left:21px;margin-right:21px;opacity:1;transition:opacity .2s}@media(max-width:600px)and (min-height:450.01px){.channels-container[data-v-5395b00e]{margin-left:0;margin-right:0}}.channels-container--loading[data-v-5395b00e]{opacity:0}.channels-container .channels-tab[data-v-5395b00e]{position:sticky;flex:none;top:65px;padding-top:2px;padding-bottom:12px;background:var(--v-background-base);z-index:1}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-tab[data-v-5395b00e]{top:0;padding-top:0;padding-bottom:8px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-tab[data-v-5395b00e]{top:0;padding-top:0;padding-bottom:10px}}.channels-container .channels-tab .channels-tab__item[data-v-5395b00e]{width:98px;padding:0;color:var(--v-text-base)!important;font-size:16px;text-transform:none}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-tab .channels-tab__item[data-v-5395b00e]{font-size:15px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-tab .channels-tab__item[data-v-5395b00e]{width:auto;font-size:15px}}.channels-container .channels-list[data-v-5395b00e]{padding-bottom:20px;background:transparent!important;overflow:inherit}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list[data-v-5395b00e]{padding-bottom:12px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list[data-v-5395b00e]{padding-left:8px;padding-right:8px;padding-bottom:12px}}.channels-container .channels-list .channels[data-v-5395b00e]{display:grid;grid-template-columns:repeat(auto-fit,minmax(365px,1fr));grid-row-gap:12px;grid-column-gap:16px;justify-content:center;background:var(--v-background-base);will-change:transform}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels[data-v-5395b00e]{grid-row-gap:10px;grid-template-columns:1fr}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels[data-v-5395b00e]{grid-row-gap:8px;grid-template-columns:1fr}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels[data-v-5395b00e]{grid-row-gap:10px;grid-template-columns:1fr}}@media(hover:none){.channels-container .channels-list .channels[data-v-5395b00e]{content-visibility:auto;contain-intrinsic-height:2000px}}@media(min-width:1630px){.channels-container .channels-list .channels[data-v-5395b00e]{grid-template-columns:repeat(auto-fit,445px)}}.channels-container .channels-list .channels.channels--length-0.channels--tab-ピン留め[data-v-5395b00e]{display:flex;min-height:calc(100vh - 180px);min-height:calc(100dvh - 180px)}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels.channels--length-0.channels--tab-ピン留め[data-v-5395b00e]{min-height:calc(100vh - 66px);min-height:calc(100dvh - 66px)}}@media(min-width:1008px){.channels-container .channels-list .channels.channels--length-1[data-v-5395b00e]{margin-right:calc(50% + 8px)}}@media(min-width:1389px){.channels-container .channels-list .channels.channels--length-1[data-v-5395b00e]{margin-right:calc(66.66667% + 10.66667px)}}@media(min-width:1630px){.channels-container .channels-list .channels.channels--length-1[data-v-5395b00e]{margin-right:922px}}@media(min-width:2090px){.channels-container .channels-list .channels.channels--length-1[data-v-5395b00e]{margin-right:1383px}}@media(min-width:1389px){.channels-container .channels-list .channels.channels--length-2[data-v-5395b00e]{margin-right:calc(33.33333% + 5.33333px)}}@media(min-width:1630px){.channels-container .channels-list .channels.channels--length-2[data-v-5395b00e]{margin-right:461px}}@media(min-width:2090px){.channels-container .channels-list .channels.channels--length-2[data-v-5395b00e]{margin-right:922px}.channels-container .channels-list .channels.channels--length-3[data-v-5395b00e]{margin-right:461px}}.channels-container .channels-list .channels .channel[data-v-5395b00e]{display:flex;flex-direction:column;position:relative;height:270px;padding:18px 20px;padding-bottom:19px;border-radius:14px;color:var(--v-text-base);background:var(--v-background-lighten1);transition:background-color .15s;overflow:hidden;text-decoration:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer;content-visibility:auto;contain-intrinsic-height:233px}@media(max-width:1007.9px){.channels-container .channels-list .channels .channel[data-v-5395b00e]{height:auto}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel[data-v-5395b00e]{padding:14px 16px;padding-top:12px;height:auto;border-radius:11px;contain-intrinsic-height:162.25px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel[data-v-5395b00e]{padding:12px 14px;padding-top:10px;height:auto;border-radius:11px;contain-intrinsic-height:125px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel[data-v-5395b00e]{padding:12px 14px;padding-top:10px;height:auto;border-radius:11px;contain-intrinsic-height:162.25px}}.channels-container .channels-list .channels .channel[data-v-5395b00e]:hover{background:var(--v-background-lighten2)}@media(hover:none){.channels-container .channels-list .channels .channel[data-v-5395b00e]:hover{background:var(--v-background-lighten1)}}.channels-container .channels-list .channels .channel .channel__broadcaster[data-v-5395b00e]{display:flex;height:44px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel .channel__broadcaster[data-v-5395b00e]{height:40px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__broadcaster[data-v-5395b00e]{height:29px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__broadcaster[data-v-5395b00e]{height:40px}}.channels-container .channels-list .channels .channel .channel__broadcaster-icon[data-v-5395b00e]{display:inline-block;flex-shrink:0;width:80px;height:44px;border-radius:5px;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2));-o-object-fit:cover;object-fit:cover}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel .channel__broadcaster-icon[data-v-5395b00e]{width:69px;height:40px;border-radius:4px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__broadcaster-icon[data-v-5395b00e]{width:54px;height:29px;border-radius:4px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__broadcaster-icon[data-v-5395b00e]{width:69px;height:40px;border-radius:4px}}.channels-container .channels-list .channels .channel .channel__broadcaster-content[data-v-5395b00e]{display:flex;flex-direction:column;margin-left:16px;width:100%;min-width:0}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel .channel__broadcaster-content[data-v-5395b00e]{margin-left:14px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__broadcaster-content[data-v-5395b00e]{align-items:center;flex-direction:row;margin-left:12px;margin-right:6px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__broadcaster-content[data-v-5395b00e]{margin-left:14px}}.channels-container .channels-list .channels .channel .channel__broadcaster-name[data-v-5395b00e]{flex-shrink:0;font-size:18px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel .channel__broadcaster-name[data-v-5395b00e]{font-size:15.5px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__broadcaster-name[data-v-5395b00e]{font-size:15px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__broadcaster-name[data-v-5395b00e]{font-size:15.5px}}.channels-container .channels-list .channels .channel .channel__broadcaster-status[data-v-5395b00e]{display:flex;position:relative;top:-1.5px;flex-shrink:0;align-items:center;margin-top:2px;font-size:12px;color:var(--v-text-darken1)}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel .channel__broadcaster-status[data-v-5395b00e]{margin-top:2px;font-size:11px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__broadcaster-status[data-v-5395b00e]{margin-top:3px;margin-left:auto;font-size:12px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__broadcaster-status[data-v-5395b00e]{margin-top:2px;font-size:11px}}.channels-container .channels-list .channels .channel .channel__broadcaster-status-force[data-v-5395b00e],.channels-container .channels-list .channels .channel .channel__broadcaster-status-viewers[data-v-5395b00e]{display:flex;align-items:center}@media(max-width:680px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__broadcaster-status-force span[data-v-5395b00e]:nth-child(2),.channels-container .channels-list .channels .channel .channel__broadcaster-status-force span[data-v-5395b00e]:nth-child(4),.channels-container .channels-list .channels .channel .channel__broadcaster-status-viewers span[data-v-5395b00e]:nth-child(2),.channels-container .channels-list .channels .channel .channel__broadcaster-status-viewers span[data-v-5395b00e]:nth-child(4){display:none}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__broadcaster-status-viewers[data-v-5395b00e]{margin-left:8px!important}}.channels-container .channels-list .channels .channel .channel__broadcaster-status-force--festival[data-v-5395b00e]{color:#e7556e}.channels-container .channels-list .channels .channel .channel__broadcaster-status-force--so-many[data-v-5395b00e]{color:#e76b55}.channels-container .channels-list .channels .channel .channel__broadcaster-status-force--many[data-v-5395b00e]{color:#e7a355}.channels-container .channels-list .channels .channel .channel__broadcaster-pin[data-v-5395b00e]{display:flex;align-items:center;justify-content:center;flex-shrink:0;position:relative;top:-5px;right:-5px;width:34px;height:34px;padding:4px;color:var(--v-text-darken1);border-radius:50%;transition:color .15s ease,background-color .15s ease;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__broadcaster-pin[data-v-5395b00e]{top:-1px}}.channels-container .channels-list .channels .channel .channel__broadcaster-pin[data-v-5395b00e]:before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;border-radius:inherit;background-color:currentColor;color:inherit;opacity:0;transition:opacity .2s cubic-bezier(.4,0,.6,1);pointer-events:none}.channels-container .channels-list .channels .channel .channel__broadcaster-pin[data-v-5395b00e]:hover{color:var(--v-text-base)}.channels-container .channels-list .channels .channel .channel__broadcaster-pin[data-v-5395b00e]:hover:before{opacity:.15}.channels-container .channels-list .channels .channel .channel__broadcaster-pin--pinned[data-v-5395b00e]{color:var(--v-primary-base)}.channels-container .channels-list .channels .channel .channel__broadcaster-pin--pinned[data-v-5395b00e]:hover{color:var(--v-primary-lighten1)}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__broadcaster-pin--pinned[data-v-5395b00e]{color:var(--v-secondary-lighten2)}.channels-container .channels-list .channels .channel .channel__broadcaster-pin--pinned[data-v-5395b00e]:hover{color:var(--v-secondary-lighten3)}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__broadcaster-pin--pinned[data-v-5395b00e]{color:var(--v-secondary-lighten2)}.channels-container .channels-list .channels .channel .channel__broadcaster-pin--pinned[data-v-5395b00e]:hover{color:var(--v-secondary-lighten3)}}.channels-container .channels-list .channels .channel .channel__program-present[data-v-5395b00e]{display:flex;flex-direction:column}.channels-container .channels-list .channels .channel .channel__program-present-title-wrapper[data-v-5395b00e]{margin-top:14px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel .channel__program-present-title-wrapper[data-v-5395b00e]{display:flex;flex-direction:column;margin-top:8px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__program-present-title-wrapper[data-v-5395b00e]{display:flex;align-items:center;margin-top:8px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__program-present-title-wrapper[data-v-5395b00e]{display:flex;flex-direction:column;margin-top:8px}}.channels-container .channels-list .channels .channel .channel__program-present-title[data-v-5395b00e]{display:-webkit-box;font-size:16px;font-weight:700;font-feature-settings:"palt" 1;letter-spacing:.07em;overflow:hidden;-webkit-line-clamp:2;-webkit-box-orient:vertical}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel .channel__program-present-title[data-v-5395b00e]{font-size:14px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__program-present-title[data-v-5395b00e]{font-size:14px;-webkit-line-clamp:1}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__program-present-title[data-v-5395b00e]{font-size:14px;-webkit-line-clamp:1}}.channels-container .channels-list .channels .channel .channel__program-present-time[data-v-5395b00e]{margin-top:4px;color:var(--v-text-darken1);font-size:13.5px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel .channel__program-present-time[data-v-5395b00e]{flex-shrink:0;margin-top:2px;font-size:13px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__program-present-time[data-v-5395b00e]{flex-shrink:0;margin-top:0;margin-left:auto;padding-left:10px;font-size:12px}}@media(max-width:680px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__program-present-time[data-v-5395b00e]{font-size:11px;padding-left:6px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__program-present-time[data-v-5395b00e]{flex-shrink:0;margin-top:1px;font-size:12px}}.channels-container .channels-list .channels .channel .channel__program-present-description[data-v-5395b00e]{display:-webkit-box;margin-top:6px;color:var(--v-text-darken1);font-size:10.5px;line-height:175%;overflow-wrap:break-word;font-feature-settings:"palt" 1;letter-spacing:.07em;overflow:hidden;-webkit-line-clamp:3;-webkit-box-orient:vertical}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list .channels .channel .channel__program-present-description[data-v-5395b00e]{margin-top:4px;font-size:11px;line-height:155%;-webkit-line-clamp:2}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__program-present-description[data-v-5395b00e]{margin-top:3px;font-size:10px;line-height:160%;-webkit-line-clamp:2}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__program-present-description[data-v-5395b00e]{margin-top:4px;font-size:10px;line-height:155%;-webkit-line-clamp:2}}.channels-container .channels-list .channels .channel .channel__program-following[data-v-5395b00e]{display:flex;flex-direction:column;color:var(--v-text-base);font-size:12.5px}@media(max-width:1007.9px){.channels-container .channels-list .channels .channel .channel__program-following[data-v-5395b00e]{margin-top:6px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__program-following[data-v-5395b00e]{flex-direction:row;margin-top:4px;font-size:12px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__program-following[data-v-5395b00e]{margin-top:4px;font-size:12px}}.channels-container .channels-list .channels .channel .channel__program-following-title[data-v-5395b00e]{display:flex;align-items:center;min-width:0}.channels-container .channels-list .channels .channel .channel__program-following-title-decorate[data-v-5395b00e]{flex-shrink:0;font-weight:700}.channels-container .channels-list .channels .channel .channel__program-following-title-icon[data-v-5395b00e]{flex-shrink:0;margin-left:3px}.channels-container .channels-list .channels .channel .channel__program-following-title-text[data-v-5395b00e]{margin-left:2px;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.channels-container .channels-list .channels .channel .channel__program-following-time[data-v-5395b00e]{color:var(--v-text-darken1)}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__program-following-time[data-v-5395b00e]{flex-shrink:0;margin-left:auto;padding-left:8px;font-size:11.5px}}@media(max-width:680px)and (max-height:450px){.channels-container .channels-list .channels .channel .channel__program-following-time[data-v-5395b00e]{font-size:11px;padding-left:6px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list .channels .channel .channel__program-following-time[data-v-5395b00e]{flex-shrink:0;font-size:11.5px}}.channels-container .channels-list .channels .channel .channel__progressbar[data-v-5395b00e]{position:absolute;left:0;right:0;bottom:0;height:4px;background:var(--v-gray-base)}.channels-container .channels-list .channels .channel .channel__progressbar-progress[data-v-5395b00e]{height:4px;background:var(--v-primary-base);transition:width .3s}.channels-container .pinned-container br[data-v-5395b00e]{display:none}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .pinned-container h2[data-v-5395b00e]{font-size:21px!important}.channels-container .pinned-container div[data-v-5395b00e]{font-size:12.5px!important;text-align:center}}@media(max-width:1000px)and (max-height:450px){.channels-container .pinned-container[data-v-5395b00e]{padding-top:12px}.channels-container .pinned-container h2[data-v-5395b00e]{font-size:21px!important}.channels-container .pinned-container div[data-v-5395b00e]{font-size:13px!important;text-align:center}.channels-container .pinned-container div .mt-4[data-v-5395b00e]{margin-top:12px!important}.channels-container .pinned-container div svg[data-v-5395b00e]{width:16px}}@media(max-width:680px)and (max-height:450px){.channels-container .pinned-container h2[data-v-5395b00e]{font-size:16px!important}.channels-container .pinned-container div[data-v-5395b00e]{font-size:10.5px!important}.channels-container .pinned-container div .mt-4[data-v-5395b00e]{margin-top:8px!important}}@media(max-width:600px)and (min-height:450.01px){.channels-container .pinned-container[data-v-5395b00e]{min-height:calc(100vh - 132px);min-height:calc(100dvh - 132px);padding-top:12px}.channels-container .pinned-container br[data-v-5395b00e]{display:inline}.channels-container .pinned-container h2[data-v-5395b00e]{font-size:22px!important;text-align:center}.channels-container .pinned-container div[data-v-5395b00e]{font-size:15px!important;text-align:center}.channels-container .pinned-container div .mt-4[data-v-5395b00e]{margin-top:8px!important}.channels-container .pinned-container div svg[data-v-5395b00e]{width:20px}}.channels-container.channels-container--watch .v-tabs-bar{height:42px;background:linear-gradient(to bottom,var(--v-background-base) calc(100% - 3px),var(--v-background-lighten1) 3px)}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container.channels-container--watch .v-tabs-bar{height:50px}}@media(max-width:1000px)and (max-height:450px){.channels-container.channels-container--watch .v-tabs-bar{height:44px}}@media(max-width:600px)and (min-height:450.01px){.channels-container.channels-container--watch .v-tabs-bar{height:46px}}.channels-container.channels-container--watch .v-tabs-bar .v-slide-group__next,.channels-container.channels-container--watch .v-tabs-bar .v-slide-group__prev{flex:auto!important;min-width:28px!important}.channels-container.channels-container--watch .v-tabs-slider-wrapper{height:3px!important;transition:left .3s cubic-bezier(.25,.8,.5,1)}.channels-container[data-v-3b3e1928]{display:flex;flex-direction:column}.channels-container .channels-tab[data-v-3b3e1928]{position:sticky;flex:none;top:0;padding-left:16px;padding-right:16px;padding-bottom:14px;background:var(--v-background-base);z-index:1}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-tab[data-v-3b3e1928]{padding-left:24px;padding-right:24px;padding-bottom:10px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-tab[data-v-3b3e1928]{padding-bottom:8px;margin-top:0}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-tab[data-v-3b3e1928]{padding-bottom:8px;margin-top:0}}.channels-container .channels-tab .channels-tab__item[data-v-3b3e1928]{min-width:72px!important;padding:0 8px;color:var(--v-text-base)!important;font-size:15px;text-transform:none}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-tab .channels-tab__item[data-v-3b3e1928]{font-size:14.5px}}.channels-container .channels-list-container[data-v-3b3e1928]{overflow-y:auto}.channels-container .channels-list-container .channels-list[data-v-3b3e1928]{padding-left:16px;padding-right:10px;padding-bottom:16px;background:transparent!important;overflow:visible!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.channels-container .channels-list-container .channels-list[data-v-3b3e1928]{padding-left:24px;padding-right:24px}}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list[data-v-3b3e1928]{padding-bottom:12px}}.channels-container .channels-list-container .channels-list .channels[data-v-3b3e1928]{display:flex;justify-content:center;flex-direction:column;will-change:transform}@media(hover:none){.channels-container .channels-list-container .channels-list .channels[data-v-3b3e1928]{content-visibility:auto;contain-intrinsic-size:319.3px 2000px}}@media(hover:none)and (max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels[data-v-3b3e1928]{contain-intrinsic-size:277.3px 2000px}}@media(min-width:1630px){.channels-container .channels-list-container .channels-list .channels[data-v-3b3e1928]{grid-template-columns:repeat(auto-fit,445px)}}.channels-container .channels-list-container .channels-list .channels .channel[data-v-3b3e1928]{display:flex;flex-direction:column;position:relative;margin-top:12px;padding:10px 12px 14px 12px;border-radius:10px;color:var(--v-text-base);background:var(--v-background-lighten1);transition:background-color .15s;overflow:hidden;text-decoration:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer;content-visibility:auto;contain-intrinsic-size:295.3px 137.3px}.channels-container .channels-list-container .channels-list .channels .channel[data-v-3b3e1928]:first-of-type{margin-top:0}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels .channel[data-v-3b3e1928]{margin-top:8px;padding:8px 12px 12px 12px;border-radius:8px;contain-intrinsic-size:253.3px 107.2px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list-container .channels-list .channels .channel[data-v-3b3e1928]{margin-top:8px;padding:8px 12px 12px 12px;border-radius:8px;contain-intrinsic-size:253.3px 107.2px}}.channels-container .channels-list-container .channels-list .channels .channel[data-v-3b3e1928]:hover{background:var(--v-background-lighten2)}@media(hover:none){.channels-container .channels-list-container .channels-list .channels .channel[data-v-3b3e1928]:hover{background:var(--v-background-lighten1)}}.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster[data-v-3b3e1928]{display:flex;height:28px}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster[data-v-3b3e1928]{height:24px}}.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-icon[data-v-3b3e1928]{display:inline-block;flex-shrink:0;width:48px;height:100%;border-radius:4px;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2));-o-object-fit:cover;object-fit:cover}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-icon[data-v-3b3e1928]{width:46px}}.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-content[data-v-3b3e1928]{display:flex;align-items:center;margin-left:12px;width:100%;min-width:0}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-content[data-v-3b3e1928]{margin-left:8px}}.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-name[data-v-3b3e1928]{font-size:14.5px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-name[data-v-3b3e1928]{font-size:13.5px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-name[data-v-3b3e1928]{font-size:13.5px}}.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-force[data-v-3b3e1928]{display:flex;align-items:center;flex-shrink:0;margin-top:2px;margin-left:auto;padding-left:6px;font-size:12px;color:var(--v-text-darken1)}.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-force--festival[data-v-3b3e1928]{color:#e7556e}.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-force--so-many[data-v-3b3e1928]{color:#e76b55}.channels-container .channels-list-container .channels-list .channels .channel .channel__broadcaster-force--many[data-v-3b3e1928]{color:#e7a355}.channels-container .channels-list-container .channels-list .channels .channel .channel__program-present[data-v-3b3e1928]{display:flex;flex-direction:column}.channels-container .channels-list-container .channels-list .channels .channel .channel__program-present-title[data-v-3b3e1928]{display:-webkit-box;margin-top:8px;font-size:13.5px;font-weight:700;font-feature-settings:"palt" 1;letter-spacing:.07em;overflow:hidden;-webkit-line-clamp:2;-webkit-box-orient:vertical}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels .channel .channel__program-present-title[data-v-3b3e1928]{margin-top:5px;font-size:12.5px;-webkit-line-clamp:1}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list-container .channels-list .channels .channel .channel__program-present-title[data-v-3b3e1928]{margin-top:5px;font-size:12.5px;-webkit-line-clamp:1}}.channels-container .channels-list-container .channels-list .channels .channel .channel__program-present-time[data-v-3b3e1928]{margin-top:4px;color:var(--v-text-darken1);font-size:11.5px}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels .channel .channel__program-present-time[data-v-3b3e1928]{margin-top:1px;font-size:10px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list-container .channels-list .channels .channel .channel__program-present-time[data-v-3b3e1928]{margin-top:1px;font-size:10px}}.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following[data-v-3b3e1928]{display:flex;flex-direction:column;margin-top:4px;color:var(--v-text-darken1);font-size:11.5px}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following[data-v-3b3e1928]{margin-top:2px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following[data-v-3b3e1928]{margin-top:2px}}.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following-title[data-v-3b3e1928]{display:flex;align-items:center}.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following-title-decorate[data-v-3b3e1928]{flex-shrink:0}.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following-title-icon[data-v-3b3e1928]{flex-shrink:0;margin-left:3px}.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following-title-text[data-v-3b3e1928]{margin-left:2px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following-time[data-v-3b3e1928]{margin-top:1px}@media(max-width:1000px)and (max-height:450px){.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following-time[data-v-3b3e1928]{font-size:10px}}@media(max-width:600px)and (min-height:450.01px){.channels-container .channels-list-container .channels-list .channels .channel .channel__program-following-time[data-v-3b3e1928]{font-size:10px}}.channels-container .channels-list-container .channels-list .channels .channel .channel__progressbar[data-v-3b3e1928]{position:absolute;left:0;right:0;bottom:0;height:4px;background:var(--v-gray-base)}.channels-container .channels-list-container .channels-list .channels .channel .channel__progressbar-progress[data-v-3b3e1928]{height:4px;background:var(--v-primary-base);transition:width .3s}.comment-container[data-v-6f8c784f]{display:flex;flex-direction:column}.comment-container .comment-header[data-v-6f8c784f]{display:flex;align-items:center;flex-shrink:0;width:100%;height:26px;padding-left:16px;padding-right:16px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.comment-container .comment-header[data-v-6f8c784f]{margin-top:20px;padding-left:24px;padding-right:24px}}@media(max-width:1000px)and (max-height:450px){.comment-container .comment-header[data-v-6f8c784f]{margin-top:12px}}@media(max-width:600px)and (min-height:450.01px){.comment-container .comment-header[data-v-6f8c784f]{margin-top:14px}}.comment-container .comment-header__title[data-v-6f8c784f]{display:flex;align-items:center;font-size:18.5px;font-weight:700;line-height:145%}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.comment-container .comment-header__title[data-v-6f8c784f]{font-size:19px}}@media(max-width:1000px)and (max-height:450px){.comment-container .comment-header__title[data-v-6f8c784f]{font-size:16.5px}}@media(max-width:600px)and (min-height:450.01px){.comment-container .comment-header__title[data-v-6f8c784f]{font-size:17px}}.comment-container .comment-header__title-icon[data-v-6f8c784f]{margin-bottom:-3px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.comment-container .comment-header__title-icon[data-v-6f8c784f]{width:24px;height:24px}}@media(max-width:1000px)and (max-height:450px){.comment-container .comment-header__title-icon[data-v-6f8c784f]{height:17.5px}}@media(max-width:600px)and (min-height:450.01px){.comment-container .comment-header__title-icon[data-v-6f8c784f]{height:18px}}.comment-container .comment-header__title-text[data-v-6f8c784f]{margin-left:12px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.comment-container .comment-header__title-text[data-v-6f8c784f]{margin-left:16px}}.comment-container .comment-header__button[data-v-6f8c784f]{display:flex;align-items:center;height:26px;padding:0 9px;border-radius:4px;background:var(--v-background-lighten3);font-size:11px;line-height:1.8;letter-spacing:0}.comment-container .comment-list-wrapper[data-v-6f8c784f]{position:relative;width:100%;height:100%;min-height:0;margin-top:16px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.comment-container .comment-list-wrapper[data-v-6f8c784f]{margin-top:20px}}@media(max-width:1000px)and (max-height:450px){.comment-container .comment-list-wrapper[data-v-6f8c784f]{margin-top:12px}}@media(max-width:600px)and (min-height:450.01px){.comment-container .comment-list-wrapper[data-v-6f8c784f]{margin-top:14px}}.comment-container .comment-list-wrapper .comment-list-dropdown[data-v-6f8c784f]{display:inline-block;position:absolute;top:var(--comment-list-dropdown-top,0);right:16px;border-radius:4px;overflow-x:hidden;overflow-y:auto;box-shadow:0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12);opacity:0;visibility:hidden;transition:opacity .15s ease,visibility .15s ease;z-index:8}.comment-container .comment-list-wrapper .comment-list-dropdown--display[data-v-6f8c784f]{opacity:1;visibility:visible}.comment-container .comment-list-wrapper .comment-list-cover[data-v-6f8c784f]{display:none;position:absolute;top:0;left:0;width:100%;height:100%;z-index:7}.comment-container .comment-list-wrapper .comment-list-cover--display[data-v-6f8c784f]{display:block}.comment-container .comment-list-wrapper .comment-list[data-v-6f8c784f]{width:100%;height:100%;padding-left:16px;padding-right:10px;padding-bottom:12px;overflow-y:scroll!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.comment-container .comment-list-wrapper .comment-list[data-v-6f8c784f]{padding-left:24px;padding-right:18px;padding-bottom:0}}@media(max-width:600px)and (min-height:450.01px){.comment-container .comment-list-wrapper .comment-list[data-v-6f8c784f]{padding-bottom:0}}.comment-container .comment-list-wrapper .comment-list .comment[data-v-6f8c784f]{display:flex;position:relative;align-items:center;min-height:28px;padding-top:6px;word-break:break-all}.comment-container .comment-list-wrapper .comment-list .comment--my-post[data-v-6f8c784f]{color:var(--v-secondary-lighten2)}.comment-container .comment-list-wrapper .comment-list .comment__text[data-v-6f8c784f]{font-size:13px}.comment-container .comment-list-wrapper .comment-list .comment__time[data-v-6f8c784f]{flex-shrink:0;margin-left:auto;padding-left:8px;color:var(--v-text-darken1);font-size:13px}.comment-container .comment-list-wrapper .comment-list .comment__icon[data-v-6f8c784f]{width:20px;height:20px;margin-left:8px;border-radius:5px;color:var(--v-text-base);cursor:pointer}.comment-container .comment-list-wrapper .comment-announce[data-v-6f8c784f]{display:flex;align-items:center;justify-content:center;flex-direction:column;position:absolute;top:0;left:0;width:100%;height:100%;padding-left:12px;padding-right:12px}.comment-container .comment-list-wrapper .comment-announce__heading[data-v-6f8c784f]{font-size:20px;font-weight:700}@media(max-width:1000px)and (max-height:450px){.comment-container .comment-list-wrapper .comment-announce__heading[data-v-6f8c784f]{font-size:16px}}.comment-container .comment-list-wrapper .comment-announce__text[data-v-6f8c784f]{margin-top:12px;color:var(--v-text-darken1);font-size:13.5px;text-align:center}@media(max-width:1000px)and (max-height:450px){.comment-container .comment-list-wrapper .comment-announce__text[data-v-6f8c784f]{font-size:12px}}.comment-container .comment-scroll-button[data-v-6f8c784f]{display:flex;align-items:center;justify-content:center;position:absolute;left:0;right:0;bottom:22px;width:42px;height:42px;margin:0 auto;border-radius:50%;background:var(--v-primary-base);transition:background-color .15s,opacity .3s,visibility .3s;visibility:hidden;opacity:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer}.comment-container .comment-scroll-button--display[data-v-6f8c784f]{opacity:1;visibility:visible}.program-container[data-v-12609bdf]{padding-left:16px;padding-right:16px;overflow-y:auto}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.program-container[data-v-12609bdf]{padding-left:24px;padding-right:24px}}.program-container .program-broadcaster[data-v-12609bdf]{display:none;align-items:center;min-width:0}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.program-container .program-broadcaster[data-v-12609bdf]{display:flex;margin-top:20px}}@media(max-width:1000px)and (max-height:450px){.program-container .program-broadcaster[data-v-12609bdf]{display:flex;margin-top:16px}}@media(max-width:600px)and (min-height:450.01px){.program-container .program-broadcaster[data-v-12609bdf]{display:flex;margin-top:16px}}.program-container .program-broadcaster__icon[data-v-12609bdf]{display:inline-block;flex-shrink:0;width:43px;height:24px;border-radius:3px;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2));-o-object-fit:cover;object-fit:cover;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.program-container .program-broadcaster__icon[data-v-12609bdf]{width:58px;height:32px}}@media(max-width:1000px)and (max-height:450px){.program-container .program-broadcaster__icon[data-v-12609bdf]{width:42px;height:23.5px}}@media(max-width:600px)and (min-height:450.01px){.program-container .program-broadcaster__icon[data-v-12609bdf]{width:58px;height:32px}}.program-container .program-broadcaster__number[data-v-12609bdf]{flex-shrink:0;margin-left:12px;font-size:16.5px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.program-container .program-broadcaster__number[data-v-12609bdf]{margin-left:16px;font-size:19px}}.program-container .program-broadcaster__name[data-v-12609bdf]{margin-left:5px;font-size:16.5px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.program-container .program-broadcaster__name[data-v-12609bdf]{margin-left:8px;font-size:19px}}@media(max-width:600px)and (min-height:450.01px){.program-container .program-broadcaster__name[data-v-12609bdf]{font-size:18px}}.program-container .program-info .program-info__title[data-v-12609bdf]{font-size:22px;font-weight:700;line-height:145%;font-feature-settings:"palt" 1;letter-spacing:.05em}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.program-container .program-info .program-info__title[data-v-12609bdf]{margin-top:16px}}@media(max-width:1000px)and (max-height:450px){.program-container .program-info .program-info__title[data-v-12609bdf]{margin-top:10px;font-size:18px}}@media(max-width:600px)and (min-height:450.01px){.program-container .program-info .program-info__title[data-v-12609bdf]{margin-top:16px;font-size:19px}}.program-container .program-info .program-info__time[data-v-12609bdf]{margin-top:8px;color:var(--v-text-darken1);font-size:14px}@media(max-width:1000px)and (max-height:450px){.program-container .program-info .program-info__time[data-v-12609bdf]{font-size:13px}}.program-container .program-info .program-info__description[data-v-12609bdf]{margin-top:12px;color:var(--v-text-darken1);font-size:12px;line-height:168%;overflow-wrap:break-word;font-feature-settings:"palt" 1;letter-spacing:.08em}@media(max-width:1000px)and (max-height:450px){.program-container .program-info .program-info__description[data-v-12609bdf]{margin-top:8px;font-size:11px}}.program-container .program-info .program-info__genre-container[data-v-12609bdf]{display:flex;flex-wrap:wrap;margin-top:10px}.program-container .program-info .program-info__genre-container .program-info__genre[data-v-12609bdf]{display:inline-block;font-size:10.5px;padding:3px;margin-top:4px;margin-right:4px;border-radius:4px;background:var(--v-background-lighten2)}@media(max-width:1000px)and (max-height:450px){.program-container .program-info .program-info__genre-container .program-info__genre[data-v-12609bdf]{font-size:9px}}.program-container .program-info .program-info__next[data-v-12609bdf]{display:flex;align-items:center;margin-top:18px;color:var(--v-text-base);font-size:14px;font-weight:700}@media(max-width:1000px)and (max-height:450px){.program-container .program-info .program-info__next[data-v-12609bdf]{margin-top:14px;font-size:13px}}.program-container .program-info .program-info__next-decorate[data-v-12609bdf]{flex-shrink:0}.program-container .program-info .program-info__next-icon[data-v-12609bdf]{flex-shrink:0;margin-left:3px;font-size:15px}.program-container .program-info .program-info__next-title[data-v-12609bdf]{display:-webkit-box;margin-top:2px;color:var(--v-text-base);font-size:14px;font-weight:700;overflow:hidden;-webkit-line-clamp:2;-webkit-box-orient:vertical}@media(max-width:1000px)and (max-height:450px){.program-container .program-info .program-info__next-title[data-v-12609bdf]{font-size:13px}}.program-container .program-info .program-info__next-time[data-v-12609bdf]{margin-top:3px;color:var(--v-text-darken1);font-size:13.5px}.program-container .program-info .program-info__status[data-v-12609bdf]{display:flex;align-items:center;margin-top:16px;font-size:14px;color:var(--v-text-darken1)}@media(max-width:1000px)and (max-height:450px){.program-container .program-info .program-info__status[data-v-12609bdf]{margin-top:10px;font-size:12px}}.program-container .program-info .program-info__status-force[data-v-12609bdf],.program-container .program-info .program-info__status-viewers[data-v-12609bdf]{display:flex;align-items:center}.program-container .program-info .program-info__status-force--festival[data-v-12609bdf]{color:#e7556e}.program-container .program-info .program-info__status-force--so-many[data-v-12609bdf]{color:#e76b55}.program-container .program-info .program-info__status-force--many[data-v-12609bdf]{color:#e7a355}.program-container .program-detail-container[data-v-12609bdf]{margin-top:24px;margin-bottom:24px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.program-container .program-detail-container[data-v-12609bdf]{margin-top:20px;margin-bottom:20px}}@media(max-width:1000px)and (max-height:450px){.program-container .program-detail-container[data-v-12609bdf]{margin-top:20px;margin-bottom:16px}}.program-container .program-detail-container .program-detail[data-v-12609bdf]{margin-top:16px}.program-container .program-detail-container .program-detail .program-detail__heading[data-v-12609bdf]{font-size:18px}@media(max-width:1000px)and (max-height:450px){.program-container .program-detail-container .program-detail .program-detail__heading[data-v-12609bdf]{font-size:16px}}.program-container .program-detail-container .program-detail .program-detail__text[data-v-12609bdf]{margin-top:8px;color:var(--v-text-darken1);font-size:12px;line-height:168%;overflow-wrap:break-word;white-space:pre-wrap;font-feature-settings:"palt" 1;letter-spacing:.08em}@media(max-width:1000px)and (max-height:450px){.program-container .program-detail-container .program-detail .program-detail__text[data-v-12609bdf]{font-size:11px}}.program-container .program-detail-container .program-detail .program-detail__text[data-v-12609bdf] a:link,.program-container .program-detail-container .program-detail .program-detail__text[data-v-12609bdf] a:visited{color:var(--v-primary-lighten1);text-underline-offset:3px}@media(max-width:1000px)and (max-height:450px){.zoom-capture-modal-container.v-dialog{width:auto!important;max-width:auto!important;aspect-ratio:16/9}}.zoom-capture-modal[data-v-62dbc198]{position:relative}.zoom-capture-modal__image[data-v-62dbc198]{display:block;width:100%;border-radius:11px}.zoom-capture-modal__download[data-v-62dbc198]{display:flex;position:absolute;align-items:center;justify-content:center;right:22px;bottom:20px;width:80px;height:80px;border-radius:50%;color:var(--v-text-base);filter:drop-shadow(0 0 4.5px rgba(0,0,0,.9))}.twitter-container[data-v-62dbc198]{display:flex;flex-direction:column;position:relative;padding-bottom:8px}@media(max-width:600px)and (min-height:450.01px){.twitter-container[data-v-62dbc198]{padding-bottom:0}}.twitter-container.watch-panel__content--active[data-v-62dbc198]{content-visibility:visible!important}.twitter-container.watch-panel__content--active .tab-container .tab-content--active[data-v-62dbc198]{opacity:1;visibility:visible}@media(hover:none){.twitter-container.watch-panel__content--active .tab-container .tab-content--active[data-v-62dbc198]{content-visibility:auto}}.twitter-container .tab-container[data-v-62dbc198]{position:relative;flex-grow:1;min-height:0}.twitter-container .tab-container .tab-content[data-v-62dbc198]{position:absolute;width:100%;height:100%;transition:opacity .2s,visibility .2s;opacity:0;visibility:hidden;overflow-y:scroll}.twitter-container .tab-container .tab-content[data-v-62dbc198]::-webkit-scrollbar{width:6px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tab-container .tab-content[data-v-62dbc198]{padding-top:16px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tab-container .tab-content[data-v-62dbc198]{padding-top:8px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tab-container .tab-content[data-v-62dbc198]{padding-top:8px}}@media(hover:none){.twitter-container .tab-container .tab-content[data-v-62dbc198]{transition:none;content-visibility:hidden}}.twitter-container .tab-container .tab-content .captures[data-v-62dbc198]{display:grid;grid-template-columns:1fr 1fr;grid-row-gap:12px;grid-column-gap:12px;padding-left:12px;padding-right:6px;max-height:100%}@supports(-webkit-touch-callout:none){.twitter-container .tab-container .tab-content .captures[data-v-62dbc198]{padding-right:12px}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tab-container .tab-content .captures[data-v-62dbc198]{grid-template-columns:1fr 1fr 1fr;padding-left:24px;padding-right:24px;grid-row-gap:10px;grid-column-gap:16px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tab-container .tab-content .captures[data-v-62dbc198]{grid-row-gap:8px;grid-column-gap:8px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tab-container .tab-content .captures[data-v-62dbc198]{grid-template-columns:1fr 1fr 1fr;grid-row-gap:10px;grid-column-gap:10px}}.twitter-container .tab-container .tab-content .captures .capture[data-v-62dbc198]{position:relative;height:82px;border-radius:11px;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2));overflow:hidden;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer;content-visibility:auto}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tab-container .tab-content .captures .capture[data-v-62dbc198]{height:90px;border-radius:9px}.twitter-container .tab-container .tab-content .captures .capture .capture__image[data-v-62dbc198]{-o-object-fit:cover;object-fit:cover}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tab-container .tab-content .captures .capture[data-v-62dbc198]{height:74px;border-radius:9px}.twitter-container .tab-container .tab-content .captures .capture .capture__image[data-v-62dbc198]{-o-object-fit:cover;object-fit:cover}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tab-container .tab-content .captures .capture[data-v-62dbc198]{height:82px;border-radius:9px}.twitter-container .tab-container .tab-content .captures .capture .capture__image[data-v-62dbc198]{-o-object-fit:cover;object-fit:cover}}.twitter-container .tab-container .tab-content .captures .capture__image[data-v-62dbc198]{display:block;width:100%;height:100%}.twitter-container .tab-container .tab-content .captures .capture__zoom[data-v-62dbc198]{display:flex;align-items:center;justify-content:center;position:absolute;top:1px;right:3px;width:38px;height:38px;border-radius:50%;filter:drop-shadow(0 0 2.5px rgba(0,0,0,.9));cursor:pointer}.twitter-container .tab-container .tab-content .captures .capture__disabled-cover[data-v-62dbc198],.twitter-container .tab-container .tab-content .captures .capture__selected-number[data-v-62dbc198]{display:none;align-items:center;justify-content:center;position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(30,19,16,.5)}.twitter-container .tab-container .tab-content .captures .capture__selected-number[data-v-62dbc198]{font-size:38px;text-shadow:0 0 2.5px rgba(0,0,0,.9)}.twitter-container .tab-container .tab-content .captures .capture__selected-checkmark[data-v-62dbc198]{display:none;position:absolute;top:6px;left:7px;width:20px;height:20px;color:var(--v-primary-base)}.twitter-container .tab-container .tab-content .captures .capture__selected-border[data-v-62dbc198]{display:none;position:absolute;top:0;left:0;right:0;bottom:0;border-radius:11px;border:4px solid var(--v-primary-base)}.twitter-container .tab-container .tab-content .captures .capture__focused-border[data-v-62dbc198]{display:none;position:absolute;top:0;left:0;right:0;bottom:0;border-radius:11px;border:4px solid var(--v-secondary-base)}.twitter-container .tab-container .tab-content .captures .capture--selected .capture__selected-border[data-v-62dbc198],.twitter-container .tab-container .tab-content .captures .capture--selected .capture__selected-checkmark[data-v-62dbc198],.twitter-container .tab-container .tab-content .captures .capture--selected .capture__selected-number[data-v-62dbc198]{display:flex}.twitter-container .tab-container .tab-content .captures .capture--focused .capture__focused-border[data-v-62dbc198]{display:block}.twitter-container .tab-container .tab-content .captures .capture--disabled[data-v-62dbc198]{cursor:auto}.twitter-container .tab-container .tab-content .captures .capture--disabled .capture__disabled-cover[data-v-62dbc198]{display:block}.twitter-container .tab-container .tab-content .capture-announce[data-v-62dbc198]{display:flex;align-items:center;justify-content:center;flex-direction:column;height:100%;padding-left:12px;padding-right:5px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tab-container .tab-content .capture-announce[data-v-62dbc198]{padding-left:24px;padding-right:24px}}.twitter-container .tab-container .tab-content .capture-announce__heading[data-v-62dbc198]{font-size:20px;font-weight:700}@media(max-width:1000px)and (max-height:450px){.twitter-container .tab-container .tab-content .capture-announce__heading[data-v-62dbc198]{font-size:16px}}.twitter-container .tab-container .tab-content .capture-announce__text[data-v-62dbc198]{margin-top:12px;color:var(--v-text-darken1);font-size:13.5px;text-align:center}@media(max-width:1000px)and (max-height:450px){.twitter-container .tab-container .tab-content .capture-announce__text[data-v-62dbc198]{font-size:12px}}.twitter-container .tab-button-container[data-v-62dbc198]{display:flex;flex-shrink:0;-moz-column-gap:7px;column-gap:7px;height:40px;margin-left:12px;margin-right:12px;padding-top:8px;padding-bottom:6px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tab-button-container[data-v-62dbc198]{height:40px;margin-left:24px;margin-right:24px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tab-button-container[data-v-62dbc198]{height:38px;margin-left:8px;margin-right:8px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tab-button-container[data-v-62dbc198]{height:38px}}.twitter-container .tab-button-container .tab-button[data-v-62dbc198]{display:flex;align-items:center;justify-content:center;flex:1;background:var(--v-background-lighten2);border-radius:7px;font-size:11px;transition:background-color .15s ease;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tab-button-container .tab-button[data-v-62dbc198]{font-size:11.5px;border-radius:7px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tab-button-container .tab-button[data-v-62dbc198]{font-size:10.5px;border-radius:6px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tab-button-container .tab-button[data-v-62dbc198]{font-size:10.5px;border-radius:6px}}.twitter-container .tab-button-container .tab-button--active[data-v-62dbc198]{background:var(--v-twitter-base)}.twitter-container .tab-button-container .tab-button__text[data-v-62dbc198]{margin-left:4px;margin-right:2px;line-height:2}.twitter-container .tweet-form[data-v-62dbc198]{display:flex;position:relative;flex-direction:column;flex-shrink:0;height:136px;margin-left:12px;margin-right:12px;border-radius:12px;border-bottom-left-radius:7px;border-bottom-right-radius:7px;background:var(--v-background-lighten1);transition:box-shadow .09s ease}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tweet-form[data-v-62dbc198]{margin-left:24px;margin-right:24px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form[data-v-62dbc198]{height:96px;margin-left:8px;margin-right:8px;border-radius:6px;border-bottom-left-radius:5px;border-bottom-right-radius:5px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form[data-v-62dbc198]{height:96px;border-radius:6px;border-bottom-left-radius:5px;border-bottom-right-radius:5px}}.twitter-container .tweet-form--focused[data-v-62dbc198]{box-shadow:0 0 0 3.5px rgba(79,130,230,.6)}.twitter-container .tweet-form--virtual-keyboard-display[data-v-62dbc198]{position:relative;bottom:calc(env(keyboard-inset-height, 0px) - 77px)}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tweet-form--virtual-keyboard-display[data-v-62dbc198]{bottom:calc(env(keyboard-inset-height, 0px) - 40px)!important}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form--virtual-keyboard-display[data-v-62dbc198]{bottom:calc(env(keyboard-inset-height, 0px) - 34px)}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form--virtual-keyboard-display[data-v-62dbc198]{bottom:calc(env(keyboard-inset-height, 0px) - 90px)!important}}.twitter-container .tweet-form__hashtag[data-v-62dbc198]{display:block;position:relative;height:19px;margin-top:12px;margin-left:12px;margin-right:12px}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__hashtag[data-v-62dbc198]{height:16px;margin-top:8px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__hashtag[data-v-62dbc198]{height:16px;margin-top:8px}}.twitter-container .tweet-form__hashtag-form[data-v-62dbc198]{display:block;width:calc(100% - 24px);height:100%;flex-grow:1;font-size:12.5px;color:var(--v-twitter-lighten2);outline:none}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__hashtag-form[data-v-62dbc198]{width:calc(100% - 22px);font-size:12px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__hashtag-form[data-v-62dbc198]{width:calc(100% - 22px);font-size:12px}}@supports(-webkit-touch-callout:none){@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__hashtag-form[data-v-62dbc198]{width:calc(133.3% - 32px);height:25.328px;font-size:16px;transform:scale(.75);transform-origin:0 0}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__hashtag-form[data-v-62dbc198]{width:calc(133.3% - 32px);height:25.328px;font-size:16px;transform:scale(.75);transform-origin:0 0}}}.twitter-container .tweet-form__hashtag-form[data-v-62dbc198]::-moz-placeholder{color:rgba(65,165,241,.6)}.twitter-container .tweet-form__hashtag-form[data-v-62dbc198]::placeholder{color:rgba(65,165,241,.6)}.twitter-container .tweet-form__hashtag-list-button[data-v-62dbc198]{display:flex;position:absolute;align-items:center;justify-content:center;top:-8px;right:-8px;width:34px;height:34px;padding:6px;border-radius:50%;color:var(--v-twitter-lighten2);cursor:pointer}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__hashtag-list-button[data-v-62dbc198]{right:-11px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__hashtag-list-button[data-v-62dbc198]{right:-11px}}.twitter-container .tweet-form__textarea[data-v-62dbc198]{display:block;flex-grow:1;margin-top:8px;margin-left:12px;margin-right:12px;font-size:12.5px;color:var(--v-text-base);word-break:break-all;resize:none;outline:none}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__textarea[data-v-62dbc198]{margin-top:6px;font-size:12px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__textarea[data-v-62dbc198]{margin-top:6px;font-size:12px}}@supports(-webkit-touch-callout:none){@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__textarea[data-v-62dbc198]{position:absolute;top:24px;left:-2px;min-width:calc(128% - 25px);min-height:34px;font-size:16px;transform:scale(.78125);transform-origin:0 0}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__textarea[data-v-62dbc198]{position:absolute;top:24px;left:-2px;min-width:calc(128% - 25px);min-height:34px;font-size:16px;transform:scale(.78125);transform-origin:0 0}}}.twitter-container .tweet-form__textarea[data-v-62dbc198]::-moz-placeholder{color:var(--v-text-darken2)}.twitter-container .tweet-form__textarea[data-v-62dbc198]::placeholder{color:var(--v-text-darken2)}.twitter-container .tweet-form__control[data-v-62dbc198]{display:flex;align-items:center;height:32px;margin-top:6px}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__control[data-v-62dbc198]{height:26px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__control[data-v-62dbc198]{height:26px}}@supports(-webkit-touch-callout:none){@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__control[data-v-62dbc198]{margin-top:auto}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__control[data-v-62dbc198]{margin-top:auto}}}.twitter-container .tweet-form__control .account-button[data-v-62dbc198]{display:flex;align-items:center;width:183px;height:100%;border-radius:7px;font-size:13px;color:var(--v-text-base);background:var(--v-background-lighten2);-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tweet-form__control .account-button[data-v-62dbc198]{width:200px;border-radius:5px;font-size:11.5px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__control .account-button[data-v-62dbc198]{width:156px;border-radius:5px;font-size:11px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__control .account-button[data-v-62dbc198]{width:auto;flex-grow:1;border-radius:5px;font-size:11.5px}}.twitter-container .tweet-form__control .account-button--no-login .account-button__screen-name[data-v-62dbc198]{font-weight:500}.twitter-container .tweet-form__control .account-button--no-login .account-button__menu[data-v-62dbc198]{display:none}.twitter-container .tweet-form__control .account-button__icon[data-v-62dbc198]{display:block;width:32px;height:100%;border-radius:7px;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2))}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__control .account-button__icon[data-v-62dbc198]{width:26px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__control .account-button__icon[data-v-62dbc198]{width:26px}}.twitter-container .tweet-form__control .account-button__screen-name[data-v-62dbc198]{flex-grow:1;line-height:2;text-align:center;font-weight:700}.twitter-container .tweet-form__control .account-button__menu[data-v-62dbc198]{margin-right:4px}.twitter-container .tweet-form__control .limit-meter[data-v-62dbc198]{display:flex;align-items:center;justify-content:center;flex-direction:column;flex-grow:1;row-gap:.5px;font-size:10px;color:var(--v-text-darken1);-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tweet-form__control .limit-meter[data-v-62dbc198]{flex-grow:1;flex-direction:row;width:auto}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__control .limit-meter[data-v-62dbc198]{font-size:9px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__control .limit-meter[data-v-62dbc198]{flex-grow:unset;flex-direction:row;width:88px}}.twitter-container .tweet-form__control .limit-meter__content[data-v-62dbc198]{display:flex;align-items:center;justify-content:center}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tweet-form__control .limit-meter__content[data-v-62dbc198]:nth-child(2){margin-left:28px;padding-right:3px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__control .limit-meter__content[data-v-62dbc198]:nth-child(2){margin-top:-2.5px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__control .limit-meter__content[data-v-62dbc198]:nth-child(2){margin-left:6px}}.twitter-container .tweet-form__control .limit-meter__content svg[data-v-62dbc198]{width:14px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tweet-form__control .limit-meter__content svg[data-v-62dbc198]{width:16px;height:16px}}.twitter-container .tweet-form__control .limit-meter__content span[data-v-62dbc198]{width:16px;margin-left:5px;text-align:center;font-weight:700}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tweet-form__control .limit-meter__content span[data-v-62dbc198]{width:25px;margin-left:8px;font-size:15px}}.twitter-container .tweet-form__control .limit-meter__content--yellow[data-v-62dbc198]{color:var(--v-warning-base)}.twitter-container .tweet-form__control .limit-meter__content--red[data-v-62dbc198]{color:var(--v-error-base)}.twitter-container .tweet-form__control .tweet-button[data-v-62dbc198]{display:flex;align-items:center;justify-content:center;width:94px;height:100%;border-radius:7px;font-size:12.5px;line-height:2;color:var(--v-text-base);background:var(--v-twitter-base);-webkit-user-select:none;-moz-user-select:none;user-select:none;outline:none;cursor:pointer}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .tweet-form__control .tweet-button[data-v-62dbc198]{width:200px;border-radius:5px;font-size:11.8px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .tweet-form__control .tweet-button[data-v-62dbc198]{width:86px;border-radius:5px;font-size:11.8px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .tweet-form__control .tweet-button[data-v-62dbc198]{width:100px;border-radius:5px;font-size:11.8px}}.twitter-container .tweet-form__control .tweet-button[disabled][data-v-62dbc198]{opacity:.7;cursor:auto}.twitter-container .hashtag-list[data-v-62dbc198]{position:absolute;left:12px;right:12px;bottom:149px;max-height:calc(100vh - 239px);max-height:calc(100dvh - 239px);padding:12px 4px;padding-bottom:10px;border-radius:7px;-webkit-clip-path:inset(0 0 0 0 round 7px);clip-path:inset(0 0 0 0 round 7px);background:var(--v-background-lighten2);box-shadow:0 3px 4px rgba(0,0,0,.53);transition:opacity .15s ease,visibility .15s ease;opacity:0;visibility:hidden;overflow-y:auto;z-index:2}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .hashtag-list[data-v-62dbc198]{left:24px;right:24px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .hashtag-list[data-v-62dbc198]{left:8px;right:8px;bottom:110px;max-height:calc(100vh - 152px);max-height:calc(100dvh - 152px);padding:6px 4px;border-radius:6px;-webkit-clip-path:inset(0 0 0 0 round 6px);clip-path:inset(0 0 0 0 round 6px)}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .hashtag-list[data-v-62dbc198]{bottom:102px;max-height:calc(100% - 110px);padding:8px 4px;border-radius:6px;-webkit-clip-path:inset(0 0 0 0 round 6px);clip-path:inset(0 0 0 0 round 6px)}}.twitter-container .hashtag-list--display[data-v-62dbc198]{opacity:1;visibility:visible}.twitter-container .hashtag-list--virtual-keyboard-display[data-v-62dbc198]{bottom:calc(env(keyboard-inset-height, 0px) - 74px)!important;max-height:calc(100vh - env(keyboard-inset-height, 0px) - 16px)!important;max-height:calc(100dvh - env(keyboard-inset-height, 0px) - 16px)!important}@media(max-width:1000px)and (max-height:450px){.twitter-container .hashtag-list--virtual-keyboard-display[data-v-62dbc198]{bottom:calc(env(keyboard-inset-height, 0px) - 26px)!important}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .hashtag-list--virtual-keyboard-display[data-v-62dbc198]{bottom:calc(env(keyboard-inset-height, 0px) - 90px)!important;max-height:calc(100% - env(keyboard-inset-height, 0px) + 82px)!important}}@media(max-width:1000px)and (max-height:450px){.twitter-container .hashtag-list[data-v-62dbc198]::-webkit-scrollbar{width:4px}}.twitter-container .hashtag-list[data-v-62dbc198]::-webkit-scrollbar-track{background:var(--v-background-lighten2)}.twitter-container .hashtag-list[data-v-62dbc198]::-webkit-scrollbar-thumb,.twitter-container .hashtag-list[data-v-62dbc198]::-webkit-scrollbar-thumb:hover{background:var(--v-gray-base)}@supports(-webkit-touch-callout:none){@media(max-width:1000px)and (max-height:450px){.twitter-container .hashtag-list[data-v-62dbc198]::-webkit-scrollbar{width:.1px;-webkit-appearance:none}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .hashtag-list[data-v-62dbc198]::-webkit-scrollbar{width:.1px;-webkit-appearance:none}}}.twitter-container .hashtag-list .hashtag-heading[data-v-62dbc198]{display:flex;align-items:center;font-weight:700;padding-left:8px;padding-right:4px}@media(max-width:1000px)and (max-height:450px){.twitter-container .hashtag-list .hashtag-heading[data-v-62dbc198]{padding-left:4px;padding-right:2px}}.twitter-container .hashtag-list .hashtag-heading__text[data-v-62dbc198]{display:flex;align-items:center;flex-grow:1;font-size:14px}.twitter-container .hashtag-list .hashtag-heading__add-button[data-v-62dbc198]{display:flex;align-items:center;font-size:13px;padding:4px 8px;border-radius:5px;outline:none;cursor:pointer}.twitter-container .hashtag-list .hashtag-container[data-v-62dbc198]{display:flex;flex-direction:column}.twitter-container .hashtag-list .hashtag-container .hashtag[data-v-62dbc198]{display:flex;position:relative!important;align-items:center;padding-top:1.5px;padding-bottom:1.5px;padding-left:8px;padding-right:4px;border-radius:7px;transition:background-color .15s ease;cursor:pointer}@media(max-width:1000px)and (max-height:450px){.twitter-container .hashtag-list .hashtag-container .hashtag[data-v-62dbc198]{padding-top:0;padding-bottom:0;padding-left:4px;padding-right:2px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .hashtag-list .hashtag-container .hashtag[data-v-62dbc198]{padding-top:0;padding-bottom:0}}.twitter-container .hashtag-list .hashtag-container .hashtag[data-v-62dbc198]:first-of-type{margin-top:6px}@media(max-width:600px)and (min-height:450.01px){.twitter-container .hashtag-list .hashtag-container .hashtag[data-v-62dbc198]:first-of-type{margin-top:0}}.twitter-container .hashtag-list .hashtag-container .hashtag[data-v-62dbc198]:hover{background:hsla(0,0%,100%,.1)}@media(hover:none){.twitter-container .hashtag-list .hashtag-container .hashtag[data-v-62dbc198]:hover{background:transparent}}.twitter-container .hashtag-list .hashtag-container .hashtag--editing[data-v-62dbc198]:hover{background:transparent}.twitter-container .hashtag-list .hashtag-container .hashtag--editing .hashtag__input[data-v-62dbc198]{box-shadow:0 0 0 3.5px rgba(79,130,230,.6);cursor:text}.twitter-container .hashtag-list .hashtag-container .hashtag__input[data-v-62dbc198]{display:block;flex-grow:1;border-radius:2px;color:var(--v-twitter-lighten2);opacity:1;outline:none;cursor:pointer;transition:box-shadow .09s ease;margin-right:4px;font-size:12.5px}@supports(-webkit-touch-callout:none){@media(max-width:1000px)and (max-height:450px){.twitter-container .hashtag-list .hashtag-container .hashtag__input[data-v-62dbc198]{position:absolute!important;left:-26px!important;width:calc(100% - 6px);margin-right:0;font-size:16px;transform:scale(.78125)}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .hashtag-list .hashtag-container .hashtag__input[data-v-62dbc198]{position:absolute!important;left:-26px!important;width:calc(100% - 21px);margin-right:0;font-size:16px;transform:scale(.78125)}}}.twitter-container .hashtag-list .hashtag-container .hashtag__edit-button[data-v-62dbc198]{margin-left:auto}.twitter-container .hashtag-list .hashtag-container .hashtag__delete-button[data-v-62dbc198],.twitter-container .hashtag-list .hashtag-container .hashtag__edit-button[data-v-62dbc198],.twitter-container .hashtag-list .hashtag-container .hashtag__sort-handle[data-v-62dbc198]{display:flex;align-items:center;justify-content:center;width:19px;height:27px;border-radius:5px;outline:none;cursor:pointer}@media(max-width:600px)and (min-height:450.01px){.twitter-container .hashtag-list .hashtag-container .hashtag__delete-button[data-v-62dbc198],.twitter-container .hashtag-list .hashtag-container .hashtag__edit-button[data-v-62dbc198],.twitter-container .hashtag-list .hashtag-container .hashtag__sort-handle[data-v-62dbc198]{width:25px}}.twitter-container .hashtag-list .hashtag-container .hashtag__sort-handle[data-v-62dbc198]{cursor:move}.twitter-container .twitter-account-list[data-v-62dbc198]{position:absolute;left:12px;right:12px;bottom:48px;max-height:calc(100vh - 137px);max-height:calc(100dvh - 137px);border-radius:7px;-webkit-clip-path:inset(0 0 0 0 round 7px);clip-path:inset(0 0 0 0 round 7px);background:var(--v-background-lighten2);box-shadow:0 3px 4px rgba(0,0,0,.53);transition:opacity .15s ease,visibility .15s ease;opacity:0;visibility:hidden;overflow-y:auto;z-index:3}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.twitter-container .twitter-account-list[data-v-62dbc198]{left:24px;right:24px}}@media(max-width:1000px)and (max-height:450px){.twitter-container .twitter-account-list[data-v-62dbc198]{left:8px;right:8px;bottom:40px;max-height:calc(100vh - 82px);max-height:calc(100dvh - 82px);border-radius:6px;-webkit-clip-path:inset(0 0 0 0 round 6px);clip-path:inset(0 0 0 0 round 6px)}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .twitter-account-list[data-v-62dbc198]{bottom:32px;max-height:calc(100% - 40px);border-radius:6px;-webkit-clip-path:inset(0 0 0 0 round 6px);clip-path:inset(0 0 0 0 round 6px)}}.twitter-container .twitter-account-list--display[data-v-62dbc198]{opacity:1;visibility:visible}.twitter-container .twitter-account-list[data-v-62dbc198]::-webkit-scrollbar-track{background:var(--v-background-lighten2)}.twitter-container .twitter-account-list[data-v-62dbc198]::-webkit-scrollbar-thumb,.twitter-container .twitter-account-list[data-v-62dbc198]::-webkit-scrollbar-thumb:hover{background:var(--v-gray-base)}.twitter-container .twitter-account-list .twitter-account[data-v-62dbc198]{display:flex;align-items:center;padding:12px 12px;border-radius:7px;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer}@media(max-width:1000px)and (max-height:450px){.twitter-container .twitter-account-list .twitter-account[data-v-62dbc198]{padding:8px 12px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .twitter-account-list .twitter-account[data-v-62dbc198]{padding:8px 12px}}.twitter-container .twitter-account-list .twitter-account__icon[data-v-62dbc198]{display:block;width:50px;height:50px;border-radius:50%}@media(max-width:1000px)and (max-height:450px){.twitter-container .twitter-account-list .twitter-account__icon[data-v-62dbc198]{width:36px;height:36px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .twitter-account-list .twitter-account__icon[data-v-62dbc198]{width:36px;height:36px}}.twitter-container .twitter-account-list .twitter-account__info[data-v-62dbc198]{display:flex;flex-direction:column;flex-grow:1;min-width:0;margin-left:12px}.twitter-container .twitter-account-list .twitter-account__name[data-v-62dbc198]{font-size:17px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}@media(max-width:1000px)and (max-height:450px){.twitter-container .twitter-account-list .twitter-account__name[data-v-62dbc198]{font-size:14px;line-height:1.3}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .twitter-account-list .twitter-account__name[data-v-62dbc198]{font-size:14px;line-height:1.3}}.twitter-container .twitter-account-list .twitter-account__screen-name[data-v-62dbc198]{color:var(--v-text-darken1);font-size:14px}@media(max-width:1000px)and (max-height:450px){.twitter-container .twitter-account-list .twitter-account__screen-name[data-v-62dbc198]{font-size:13px}}@media(max-width:600px)and (min-height:450.01px){.twitter-container .twitter-account-list .twitter-account__screen-name[data-v-62dbc198]{font-size:13px}}.twitter-container .twitter-account-list .twitter-account__check[data-v-62dbc198]{flex-shrink:0;color:var(--v-twitter-lighten1)}.watch-player__dplayer svg circle,.watch-player__dplayer svg path{fill:var(--v-text-base)!important}.watch-player__dplayer .dplayer-video-wrap{background:transparent!important}.watch-player__dplayer .dplayer-video-wrap .dplayer-video-wrap-aspect{transition:opacity .2s cubic-bezier(.4,.38,.49,.94);opacity:1}.watch-player__dplayer .dplayer-video-wrap .dplayer-danmaku{max-width:100%;max-height:calc(100% - var(--comment-area-vertical-margin, 0px));aspect-ratio:var(--comment-area-aspect-ratio,16/9);transition:max-height .5s cubic-bezier(.42,.19,.53,.87),aspect-ratio .5s cubic-bezier(.42,.19,.53,.87);will-change:aspect-ratio;overflow:hidden}.watch-player__dplayer .dplayer-video-wrap .dplayer-danloading,.watch-player__dplayer .dplayer-video-wrap .dplayer-loading-icon{display:none!important}.watch-player__dplayer .dplayer-controller-mask{height:82px!important;background:linear-gradient(180deg,transparent,#000000cf)!important;opacity:0!important;visibility:hidden;transition:opacity .3s ease,visibility .3s ease!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-player__dplayer .dplayer-controller-mask{height:66px!important}}@media(max-width:1000px)and (max-height:450px){.watch-player__dplayer .dplayer-controller-mask{height:66px!important}}@media(max-width:600px)and (min-height:450.01px){.watch-player__dplayer .dplayer-controller-mask{height:66px!important}}.watch-player__dplayer .dplayer-controller{padding-left:86px!important;padding-bottom:6px!important;transition:opacity .3s ease,visibility .3s ease;opacity:0!important;visibility:hidden}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-player__dplayer .dplayer-controller{padding-left:18px!important}}@media(max-width:1000px)and (max-height:450px){.watch-player__dplayer .dplayer-controller{padding-left:18px!important}}@media(max-width:600px)and (min-height:450.01px){.watch-player__dplayer .dplayer-controller{padding-left:18px!important}}.watch-player__dplayer .dplayer-controller .dplayer-live-badge,.watch-player__dplayer .dplayer-controller .dplayer-time{color:var(--v-text-base)!important}.watch-player__dplayer .dplayer-controller .dplayer-volume-bar{background:var(--v-text-base)!important}.watch-player__dplayer .dplayer-controller .dplayer-icons{bottom:auto!important}.watch-player__dplayer .dplayer-controller .dplayer-icons.dplayer-icons-right{right:22px!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-player__dplayer .dplayer-controller .dplayer-icons.dplayer-icons-right{right:11px!important}}@media(max-width:1000px)and (max-height:450px){.watch-player__dplayer .dplayer-controller .dplayer-icons.dplayer-icons-right{right:11px!important}}@media(max-width:600px)and (min-height:450.01px){.watch-player__dplayer .dplayer-controller .dplayer-icons.dplayer-icons-right{right:11px!important}}.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-full-in-icon{display:none!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-pip-icon:after{left:25%}.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-full-icon:after{left:-20%}}@media(max-width:1000px)and (max-height:450px){.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-pip-icon:after{left:25%}.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-full-icon:after{left:-20%}}@media(max-width:600px)and (min-height:450.01px){.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-pip-icon:after{left:25%}.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-full-icon:after{left:-20%}}.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-capture-icon,.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-comment-capture-icon{transition:background-color .08s ease;border-radius:6px}.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-capture-icon.dplayer-capturing,.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-comment-capture-icon.dplayer-capturing{background:var(--v-secondary-lighten1)}.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-capture-icon.dplayer-capturing .dplayer-icon-content,.watch-player__dplayer .dplayer-controller .dplayer-icons .dplayer-icon.dplayer-comment-capture-icon.dplayer-capturing .dplayer-icon-content{opacity:1}.watch-player__dplayer .dplayer-controller .dplayer-comment-box{transition:opacity .3s ease,visibility .3s ease!important}.watch-player__dplayer .dplayer-controller .dplayer-comment-box .dplayer-comment-setting-icon{z-index:5}.watch-player__dplayer .dplayer-controller .dplayer-comment-box .dplayer-comment-input{transition:box-shadow .09s ease;-moz-appearance:none;appearance:none;-webkit-appearance:none}.watch-player__dplayer .dplayer-controller .dplayer-comment-box .dplayer-comment-input:focus{box-shadow:0 0 0 3.5px rgba(79,130,230,.6)}@supports(-webkit-touch-callout:none){@media(max-width:1000px)and (max-height:450px){.watch-player__dplayer .dplayer-controller .dplayer-comment-box .dplayer-comment-input{width:114.2857%!important;height:114.2857%!important;font-size:16px!important;transform:scale(.875);transform-origin:0 0}}@media(max-width:600px)and (min-height:450.01px){.watch-player__dplayer .dplayer-controller .dplayer-comment-box .dplayer-comment-input{width:114.2857%!important;height:114.2857%!important;font-size:16px!important;transform:scale(.875);transform-origin:0 0}}}.watch-player__dplayer .dplayer-notice{padding:16px 22px!important;margin-right:30px;border-radius:4px!important;font-size:15px!important;line-height:1.6}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-player__dplayer .dplayer-notice{top:auto;left:16px!important;padding:12px 16px!important;margin-right:16px;font-size:13.5px!important}}@media(max-width:1000px)and (max-height:450px){.watch-player__dplayer .dplayer-notice{padding:12px 16px!important;margin-right:16px;font-size:13.5px!important}}@media(max-width:600px)and (min-height:450.01px){.watch-player__dplayer .dplayer-notice{top:auto;left:16px!important;padding:12px 16px!important;margin-right:16px;font-size:13.5px!important}}.watch-player__dplayer .dplayer-info-panel{transition:top .3s,left .3s}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-player__dplayer .dplayer-setting-box{height:calc(100% - 60px)!important}}@media(max-width:600px)and (min-height:450.01px){.watch-player__dplayer .dplayer-setting-box{height:calc(100% - 60px)!important}}.watch-player__dplayer .dplayer-setting-box .dplayer-setting-audio-panel .dplayer-setting-audio-item.dplayer-setting-audio-item--disabled{pointer-events:none}.watch-player__dplayer .dplayer-setting-box .dplayer-setting-audio-panel .dplayer-setting-audio-item.dplayer-setting-audio-item--disabled .dplayer-label{color:#aaa}.watch-player__dplayer .dplayer-comment-setting-box .dplayer-comment-setting-title{color:var(--v-text-base)}.watch-player__dplayer .dplayer-comment-setting-box .dplayer-comment-setting-size span,.watch-player__dplayer .dplayer-comment-setting-box .dplayer-comment-setting-type span{border:1px solid --v-text-base}.watch-player__dplayer .dplayer-comment-setting-box .dplayer-comment-setting-size input:checked+span,.watch-player__dplayer .dplayer-comment-setting-box .dplayer-comment-setting-type input:checked+span{background:var(--v-text-base)}.watch-player__dplayer.dplayer-mobile .dplayer-controller{padding-left:98px!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-player__dplayer.dplayer-mobile .dplayer-controller{padding-left:18px!important}}@media(max-width:1000px)and (max-height:450px){.watch-player__dplayer.dplayer-mobile .dplayer-controller{padding-left:18px!important}}@media(max-width:600px)and (min-height:450.01px){.watch-player__dplayer.dplayer-mobile .dplayer-controller{padding-left:18px!important}}.watch-player__dplayer.dplayer-mobile.dplayer-hide-controller .dplayer-controller{transform:none!important}.watch-player--loading .dplayer-video-wrap-aspect{opacity:0!important}:root .dplayer-icon:hover .dplayer-icon-content,_::-webkit-full-page-media,_:future{opacity:.8!important}.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller,.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller-mask{opacity:1!important;visibility:visible!important}.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller .dplayer-comment-box,.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller-mask .dplayer-comment-box{left:88px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller .dplayer-comment-box,.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller-mask .dplayer-comment-box{left:16px}}@media(max-width:1000px)and (max-height:450px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller .dplayer-comment-box,.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller-mask .dplayer-comment-box{left:16px}}@media(max-width:600px)and (min-height:450.01px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller .dplayer-comment-box,.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-controller-mask .dplayer-comment-box{left:16px}}.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-notice{left:98px;bottom:62px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-notice{left:16px;bottom:62px!important}}@media(max-width:1000px)and (max-height:450px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-notice{left:16px}}@media(max-width:600px)and (min-height:450.01px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-notice{left:16px;bottom:62px!important}}.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-info-panel{top:82px;left:98px}@media(min-width:1000.01px)and (max-width:1264px)and (max-height:850px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-info-panel{left:16px}}@media(max-width:1000px)and (max-height:450px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-info-panel{left:16px}}@media(max-width:600px)and (min-height:450.01px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-info-panel{left:16px}}.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-comment-setting-box{left:88px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-comment-setting-box{left:16px}}@media(max-width:1000px)and (max-height:450px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-comment-setting-box{left:16px}}@media(max-width:600px)and (min-height:450.01px){.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-comment-setting-box{left:16px}}.watch-container.watch-container--control-display .watch-player__dplayer .dplayer-mobile .dplayer-mobile-icon-wrap{opacity:.7!important;visibility:visible!important}.watch-container:not(.watch-container--control-display) .watch-player__dplayer .dplayer-danmaku{max-height:100%!important;aspect-ratio:16/9!important}.watch-container:not(.watch-container--control-display) .watch-player__dplayer .dplayer-notice{bottom:20px!important}.watch-container.watch-container--fullscreen .watch-player__dplayer .dplayer-controller{padding-left:20px!important}.watch-container.watch-container--fullscreen .watch-player__dplayer.dplayer-mobile .dplayer-controller{padding-left:30px!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container.watch-container--fullscreen .watch-player__dplayer.dplayer-mobile .dplayer-controller{padding-left:16px!important}}@media(max-width:1000px)and (max-height:450px){.watch-container.watch-container--fullscreen .watch-player__dplayer.dplayer-mobile .dplayer-controller{padding-left:16px!important}}@media(max-width:600px)and (min-height:450.01px){.watch-container.watch-container--fullscreen .watch-player__dplayer.dplayer-mobile .dplayer-controller{padding-left:16px!important}}.watch-container.watch-container--fullscreen .watch-player__dplayer .dplayer-comment-box,.watch-container.watch-container--fullscreen .watch-player__dplayer .dplayer-comment-setting-box{left:20px!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container.watch-container--fullscreen .watch-player__dplayer .dplayer-comment-box,.watch-container.watch-container--fullscreen .watch-player__dplayer .dplayer-comment-setting-box{left:16px!important}}@media(max-width:1000px)and (max-height:450px){.watch-container.watch-container--fullscreen .watch-player__dplayer .dplayer-comment-box,.watch-container.watch-container--fullscreen .watch-player__dplayer .dplayer-comment-setting-box{left:16px!important}}@media(max-width:600px)and (min-height:450.01px){.watch-container.watch-container--fullscreen .watch-player__dplayer .dplayer-comment-box,.watch-container.watch-container--fullscreen .watch-player__dplayer .dplayer-comment-setting-box{left:16px!important}}.watch-container.watch-container--fullscreen .watch-header__back-icon{display:none!important}.watch-container.watch-container--fullscreen.watch-container--control-display .watch-player__dplayer .dplayer-info-panel,.watch-container.watch-container--fullscreen.watch-container--control-display .watch-player__dplayer .dplayer-notice{left:30px!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container.watch-container--fullscreen.watch-container--control-display .watch-player__dplayer .dplayer-info-panel,.watch-container.watch-container--fullscreen.watch-container--control-display .watch-player__dplayer .dplayer-notice{left:16px!important}}@media(max-width:1000px)and (max-height:450px){.watch-container.watch-container--fullscreen.watch-container--control-display .watch-player__dplayer .dplayer-info-panel,.watch-container.watch-container--fullscreen.watch-container--control-display .watch-player__dplayer .dplayer-notice{left:16px!important}}@media(max-width:600px)and (min-height:450.01px){.watch-container.watch-container--fullscreen.watch-container--control-display .watch-player__dplayer .dplayer-info-panel,.watch-container.watch-container--fullscreen.watch-container--control-display .watch-player__dplayer .dplayer-notice{left:16px!important}}.watch-player.watch-player--virtual-keyboard-display .watch-player__dplayer .dplayer-controller-mask{position:absolute;bottom:env(keyboard-inset-height,0)!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-player.watch-player--virtual-keyboard-display .watch-player__dplayer .dplayer-controller-mask{bottom:0!important}}@media(max-width:600px)and (min-height:450.01px){.watch-player.watch-player--virtual-keyboard-display .watch-player__dplayer .dplayer-controller-mask{bottom:0!important}}.watch-player.watch-player--virtual-keyboard-display .watch-player__dplayer .dplayer-icons.dplayer-comment-box{position:absolute;bottom:calc(env(keyboard-inset-height, 0px) + 4px)!important}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-player.watch-player--virtual-keyboard-display .watch-player__dplayer .dplayer-icons.dplayer-comment-box{bottom:6px!important}}@media(max-width:600px)and (min-height:450.01px){.watch-player.watch-player--virtual-keyboard-display .watch-player__dplayer .dplayer-icons.dplayer-comment-box{bottom:6px!important}}.shortcut-key[data-v-51acc2dc]{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;min-width:32px;min-height:28px;padding:3px 8px;border-radius:5px;background-color:var(--v-background-lighten2);font-size:14.5px;text-align:center}.shortcut-key-plus[data-v-51acc2dc]{display:inline-block;margin:0 5px;flex-shrink:0}.route-container[data-v-51acc2dc]{height:100vh!important;height:100dvh!important;background:var(--v-black-base)!important;overflow:hidden}@supports(-webkit-touch-callout:none){.route-container[data-v-51acc2dc]{height:-webkit-fill-available!important}}.watch-container[data-v-51acc2dc]{display:flex;width:calc(100% + 352px);height:100%;transition:width .4s cubic-bezier(.26,.68,.55,.99)}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container[data-v-51acc2dc]{flex-direction:column;width:100%}}@media(max-width:1000px)and (max-height:450px){.watch-container[data-v-51acc2dc]{width:calc(100% + 310px)}}@media(max-width:600px)and (min-height:450.01px){.watch-container[data-v-51acc2dc]{flex-direction:column;width:100%;padding-bottom:56px}}.watch-container.watch-container--control-display .watch-content[data-v-51acc2dc]{cursor:auto!important}.watch-container.watch-container--control-display .watch-header[data-v-51acc2dc],.watch-container.watch-container--control-display .watch-navigation[data-v-51acc2dc],.watch-container.watch-container--control-display .watch-player__button[data-v-51acc2dc]{opacity:1!important;visibility:visible!important}.watch-container.watch-container--panel-display[data-v-51acc2dc]{width:100%}.watch-container.watch-container--panel-display .switch-button-panel .switch-button-icon[data-v-51acc2dc]{color:var(--v-primary-base)}@media(hover:none){.watch-container.watch-container--panel-display .watch-panel[data-v-51acc2dc]{content-visibility:auto}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container[data-v-51acc2dc]{width:100%}}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px)and (hover:none){.watch-container .watch-panel[data-v-51acc2dc]{content-visibility:auto}}@media(max-width:600px)and (min-height:450.01px){.watch-container[data-v-51acc2dc]{width:100%}}@media(max-width:600px)and (min-height:450.01px)and (hover:none){.watch-container .watch-panel[data-v-51acc2dc]{content-visibility:auto}}.watch-container.watch-container--fullscreen .watch-navigation[data-v-51acc2dc]{display:none}.watch-container.watch-container--fullscreen .watch-content .watch-header[data-v-51acc2dc]{padding-left:30px}@media(max-width:1000px)and (max-height:450px){.watch-container.watch-container--fullscreen .watch-content .watch-header[data-v-51acc2dc]{padding-left:16px}}.watch-container .watch-navigation[data-v-51acc2dc]{display:flex;flex-direction:column;position:fixed;width:68px;top:0;left:0;bottom:-100px;padding:18px 8px 122px;background:#2f221f80;transition:opacity .3s,visibility .3s;opacity:0;visibility:hidden;z-index:2}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-navigation[data-v-51acc2dc]{display:none}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-navigation[data-v-51acc2dc]{display:none}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-navigation[data-v-51acc2dc]{display:none}}.watch-container .watch-navigation .watch-navigation__icon[data-v-51acc2dc]{display:flex;justify-content:center;align-items:center;height:52px;margin-bottom:17px;border-radius:11px;font-size:16px;color:var(--v-text-base);transition:background-color .15s;text-decoration:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-navigation .watch-navigation__icon[data-v-51acc2dc]{height:32px;border-radius:10px}.watch-container .watch-navigation div.spacer[data-v-51acc2dc]{display:none}}.watch-container .watch-navigation .watch-navigation__link[data-v-51acc2dc]{display:flex;justify-content:center;align-items:center;height:52px;border-radius:11px;font-size:16px;color:var(--v-text-base);transition:background-color .15s;text-decoration:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-navigation .watch-navigation__link[data-v-51acc2dc]{height:44px;border-radius:10px}.watch-container .watch-navigation .watch-navigation__link[data-v-51acc2dc]:last-child,.watch-container .watch-navigation .watch-navigation__link[data-v-51acc2dc]:nth-last-child(2){display:none}}.watch-container .watch-navigation .watch-navigation__link[data-v-51acc2dc]:hover{background:#433532a0}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-navigation .watch-navigation__link-icon[data-v-51acc2dc]{width:26px;height:26px}}.watch-container .watch-navigation .watch-navigation__link--active[data-v-51acc2dc]{color:var(--v-primary-base);background:#433532a0}.watch-container .watch-navigation .watch-navigation__link+.watch-navigation__link[data-v-51acc2dc]{margin-top:4px}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-navigation .watch-navigation__link+.watch-navigation__link[data-v-51acc2dc]{margin-top:auto}}.watch-container .watch-content[data-v-51acc2dc]{display:flex;position:relative;width:100%;cursor:none}.watch-container .watch-content .watch-header[data-v-51acc2dc]{display:flex;align-items:center;position:absolute;top:0;left:0;width:100%;height:82px;padding-left:98px;padding-right:30px;background:linear-gradient(180deg,#000000cf,transparent);transition:opacity .3s,visibility .3s;opacity:0;visibility:hidden;z-index:1}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-content .watch-header[data-v-51acc2dc]{height:66px;padding-left:16px;padding-right:16px}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-header[data-v-51acc2dc]{padding-left:84px;padding-right:16px;height:66px;padding-left:16px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-header[data-v-51acc2dc]{display:none;height:50px;padding-left:16px;padding-right:16px}}.watch-container .watch-content .watch-header .watch-header__back-icon[data-v-51acc2dc]{display:none}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-content .watch-header .watch-header__back-icon[data-v-51acc2dc]{display:flex;position:relative!important;align-items:center;justify-content:center;flex-shrink:0;width:40px;height:40px;left:-6px;padding:6px;margin-right:2px;border-radius:50%;color:var(--v-text-base)}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-header .watch-header__back-icon[data-v-51acc2dc]{display:flex;position:relative!important;align-items:center;justify-content:center;flex-shrink:0;width:36px;height:36px;left:-6px;padding:6px;margin-right:2px;border-radius:50%;color:var(--v-text-base)}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-header .watch-header__back-icon[data-v-51acc2dc]{display:flex;position:relative!important;align-items:center;justify-content:center;flex-shrink:0;width:36px;height:36px;left:-6px;padding:6px;margin-right:2px;border-radius:50%;color:var(--v-text-base)}}.watch-container .watch-content .watch-header .watch-header__broadcaster[data-v-51acc2dc]{display:inline-block;flex-shrink:0;width:64px;height:36px;border-radius:5px;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2));-o-object-fit:cover;object-fit:cover;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-content .watch-header .watch-header__broadcaster[data-v-51acc2dc]{width:48px;height:28px;border-radius:4px}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-header .watch-header__broadcaster[data-v-51acc2dc]{width:48px;height:28px;border-radius:4px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-header .watch-header__broadcaster[data-v-51acc2dc]{display:none}}.watch-container .watch-content .watch-header .watch-header__program-title[data-v-51acc2dc]{margin-left:18px;font-size:18px;font-weight:700;font-feature-settings:"palt" 1;letter-spacing:.05em;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-content .watch-header .watch-header__program-title[data-v-51acc2dc]{margin-left:12px;font-size:16px}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-header .watch-header__program-title[data-v-51acc2dc]{margin-left:12px;font-size:16px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-header .watch-header__program-title[data-v-51acc2dc]{margin-left:0;font-size:16px}}.watch-container .watch-content .watch-header .watch-header__program-time[data-v-51acc2dc]{flex-shrink:0;margin-left:16px;font-size:15px;font-weight:600}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-header .watch-header__program-time[data-v-51acc2dc]{margin-left:8px;font-size:14px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-header .watch-header__program-time[data-v-51acc2dc]{margin-left:8px;font-size:14px}}.watch-container .watch-content .watch-header .watch-header__now[data-v-51acc2dc]{flex-shrink:0;margin-left:16px;font-size:13px;font-weight:600}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-header .watch-header__now[data-v-51acc2dc]{display:none}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-header .watch-header__now[data-v-51acc2dc]{display:none}}.watch-container .watch-content .watch-player[data-v-51acc2dc]{display:flex;position:relative;width:100%;height:100%;background-size:contain;background-position:50%}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-content .watch-player[data-v-51acc2dc]{aspect-ratio:16/9}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-player[data-v-51acc2dc]{aspect-ratio:16/9}}.watch-container .watch-content .watch-player .watch-player__background-wrapper[data-v-51acc2dc]{position:absolute;top:0;left:0;width:100%;height:100%}.watch-container .watch-content .watch-player .watch-player__background-wrapper .watch-player__background[data-v-51acc2dc]{position:relative;top:50%;left:50%;max-height:100%;aspect-ratio:16/9;transform:translate(-50%,-50%);background-blend-mode:overlay;background-color:rgba(14,14,18,.35);background-size:cover;background-image:none;opacity:0;visibility:hidden;transition:opacity .4s cubic-bezier(.4,.38,.49,.94),visibility .4s cubic-bezier(.4,.38,.49,.94);will-change:opacity}.watch-container .watch-content .watch-player .watch-player__background-wrapper .watch-player__background--display[data-v-51acc2dc]{opacity:1;visibility:visible}.watch-container .watch-content .watch-player .watch-player__background-wrapper .watch-player__background .watch-player__background-logo[data-v-51acc2dc]{display:inline-block;position:absolute;height:34px;right:56px;bottom:44px;filter:drop-shadow(0 0 5px var(--v-black-base))}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-content .watch-player .watch-player__background-wrapper .watch-player__background .watch-player__background-logo[data-v-51acc2dc]{height:30px;right:34px;bottom:30px}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-player .watch-player__background-wrapper .watch-player__background .watch-player__background-logo[data-v-51acc2dc]{height:25px;right:30px;bottom:24px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-player .watch-player__background-wrapper .watch-player__background .watch-player__background-logo[data-v-51acc2dc]{height:22px;right:30px;bottom:24px}}.watch-container .watch-content .watch-player .watch-player__buffering[data-v-51acc2dc]{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--v-background-lighten3);filter:drop-shadow(0 0 3px rgba(0,0,0,.3));opacity:0;visibility:hidden;transition:opacity .2s cubic-bezier(.4,.38,.49,.94),visibility .2s cubic-bezier(.4,.38,.49,.94);will-change:opacity;z-index:3}.watch-container .watch-content .watch-player .watch-player__buffering--display[data-v-51acc2dc]{opacity:1;visibility:visible}.watch-container .watch-content .watch-player .watch-player__dplayer[data-v-51acc2dc]{width:100%}.watch-container .watch-content .watch-player .watch-player__button[data-v-51acc2dc]{display:flex;justify-content:space-around;flex-direction:column;position:absolute;top:50%;right:28px;height:190px;transform:translateY(-50%);opacity:0;visibility:hidden;transition:opacity .3s,visibility .3s}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-content .watch-player .watch-player__button[data-v-51acc2dc]{right:15px;height:128px}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-player .watch-player__button[data-v-51acc2dc]{right:15px;height:155px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-player .watch-player__button[data-v-51acc2dc]{right:15px;height:100px}}.watch-container .watch-content .watch-player .watch-player__button .switch-button[data-v-51acc2dc]{display:flex;justify-content:center;align-items:center;width:48px;height:48px;color:var(--v-text-base);background:#2f221fc0;border-radius:7px;transition:background-color .15s;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-player .watch-player__button .switch-button[data-v-51acc2dc]{width:38px;height:38px;border-radius:5px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-player .watch-player__button .switch-button[data-v-51acc2dc]{width:38px;height:38px;border-radius:5px}}.watch-container .watch-content .watch-player .watch-player__button .switch-button[data-v-51acc2dc]:hover{background:#2f221ff0}@media(hover:none){.watch-container .watch-content .watch-player .watch-player__button .switch-button[data-v-51acc2dc]:hover{background:#2f221fc0}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-content .watch-player .watch-player__button .switch-button svg[data-v-51acc2dc]{height:27px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-player .watch-player__button .switch-button svg[data-v-51acc2dc]{height:27px}}.watch-container .watch-content .watch-player .watch-player__button .switch-button .switch-button-icon[data-v-51acc2dc]{position:relative}.watch-container .watch-content .watch-player .watch-player__button .switch-button-up>.switch-button-icon[data-v-51acc2dc]{top:6px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-content .watch-player .watch-player__button .switch-button-panel[data-v-51acc2dc]{display:none}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-content .watch-player .watch-player__button .switch-button-panel[data-v-51acc2dc]{display:none}}.watch-container .watch-content .watch-player .watch-player__button .switch-button-panel>.switch-button-icon[data-v-51acc2dc]{top:1.5px;transition:color .4s cubic-bezier(.26,.68,.55,.99)}.watch-container .watch-content .watch-player .watch-player__button .switch-button-down>.switch-button-icon[data-v-51acc2dc]{bottom:4px}.watch-container .watch-panel[data-v-51acc2dc]{display:flex;flex-direction:column;flex-shrink:0;width:352px;height:100%;background:var(--v-background-base)}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-panel[data-v-51acc2dc]{width:100%;height:auto;flex-grow:1}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-panel[data-v-51acc2dc]{width:310px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-panel[data-v-51acc2dc]{width:100%;height:auto;flex-grow:1}}@media(hover:none){.watch-container .watch-panel[data-v-51acc2dc]{content-visibility:hidden}}.watch-container .watch-panel .watch-panel__header[data-v-51acc2dc]{display:flex;align-items:center;flex-shrink:0;width:100%;height:70px;padding-left:16px;padding-right:16px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-panel .watch-panel__header[data-v-51acc2dc]{display:none}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-panel .watch-panel__header[data-v-51acc2dc]{display:none}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-panel .watch-panel__header[data-v-51acc2dc]{display:none}}.watch-container .watch-panel .watch-panel__header .panel-close-button[data-v-51acc2dc]{display:flex;position:relative;align-items:center;flex-shrink:0;left:-4px;height:35px;padding:0 4px;border-radius:5px;font-size:16px;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer}.watch-container .watch-panel .watch-panel__header .panel-close-button__icon[data-v-51acc2dc]{position:relative;left:-4px}.watch-container .watch-panel .watch-panel__header .panel-close-button__text[data-v-51acc2dc]{font-weight:700}.watch-container .watch-panel .watch-panel__header .panel-broadcaster[data-v-51acc2dc]{display:flex;align-items:center;min-width:0;margin-left:16px}.watch-container .watch-panel .watch-panel__header .panel-broadcaster__icon[data-v-51acc2dc]{display:inline-block;flex-shrink:0;width:43px;height:24px;border-radius:3px;background:linear-gradient(150deg,var(--v-gray-base),var(--v-background-lighten2));-o-object-fit:cover;object-fit:cover;-webkit-user-select:none;-moz-user-select:none;user-select:none}.watch-container .watch-panel .watch-panel__header .panel-broadcaster__number[data-v-51acc2dc]{flex-shrink:0;margin-left:8px;font-size:16px}.watch-container .watch-panel .watch-panel__header .panel-broadcaster__name[data-v-51acc2dc]{margin-left:5px;font-size:16px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-panel .watch-panel__header .panel-broadcaster__name[data-v-51acc2dc]{font-size:14px}}.watch-container .watch-panel .watch-panel__content-container[data-v-51acc2dc]{position:relative;height:100%}.watch-container .watch-panel .watch-panel__content-container .watch-panel__content[data-v-51acc2dc]{position:absolute;top:0;left:0;right:0;bottom:0;background:var(--v-background-base);transition:opacity .2s,visibility .2s;opacity:0;visibility:hidden}@media(hover:none){.watch-container .watch-panel .watch-panel__content-container .watch-panel__content[data-v-51acc2dc]{transition:none;content-visibility:hidden}}.watch-container .watch-panel .watch-panel__content-container .watch-panel__content--active[data-v-51acc2dc]{opacity:1;visibility:visible;content-visibility:auto}.watch-container .watch-panel .watch-panel__navigation[data-v-51acc2dc]{display:flex;align-items:center;justify-content:space-evenly;flex-shrink:0;height:77px;background:var(--v-background-lighten1)}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-panel .watch-panel__navigation[data-v-51acc2dc]{height:48px;background:var(--v-background-base)}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-panel .watch-panel__navigation[data-v-51acc2dc]{height:34px}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-panel .watch-panel__navigation[data-v-51acc2dc]{height:44px;background:var(--v-background-base)}}.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button[data-v-51acc2dc]{display:flex;justify-content:center;align-items:center;flex-direction:column;width:77px;height:56px;padding:6px 0;border-radius:5px;color:var(--v-text-base);box-sizing:content-box;transition:color .3s;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button[data-v-51acc2dc]{width:100px;height:40px;padding:5px 0;box-sizing:border-box}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button[data-v-51acc2dc]{height:34px;padding:5px 0;box-sizing:border-box}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button[data-v-51acc2dc]{height:34px;padding:5px 0;box-sizing:border-box}}.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button--active .panel-navigation-button__icon[data-v-51acc2dc],.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button--active[data-v-51acc2dc]{color:var(--v-primary-base)}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button--active[data-v-51acc2dc]{background:#5b2d3c}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button--active[data-v-51acc2dc]{background:#5b2d3c}}.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button__icon[data-v-51acc2dc]{height:34px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button__icon[data-v-51acc2dc]{color:var(--v-text-base)}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button__icon[data-v-51acc2dc]{color:var(--v-text-base)}}.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button__text[data-v-51acc2dc]{margin-top:5px;font-size:13px}@media(min-width:600.1px)and (max-width:850px)and (min-height:850.01px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button__text[data-v-51acc2dc]{display:none}}@media(max-width:1000px)and (max-height:450px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button__text[data-v-51acc2dc]{display:none}}@media(max-width:600px)and (min-height:450.01px){.watch-container .watch-panel .watch-panel__navigation .panel-navigation-button__text[data-v-51acc2dc]{display:none}} \ No newline at end of file diff --git a/client/dist/assets/js/app.238feba1.js b/client/dist/assets/js/app.238feba1.js new file mode 100644 index 00000000..101193ad --- /dev/null +++ b/client/dist/assets/js/app.238feba1.js @@ -0,0 +1,2 @@ +(function(){"use strict";var t={370:function(t,e,s){var i=s(2856),a=s(6086),n=s(2803),r=s(7738),o=s(144),l=s(4801),c=s(1797),_=s.n(c),d=s(1096),u=function(){var t=this,e=t._self._c;return e(d.Z,{attrs:{id:"app"}},[e("transition",[e("router-view")],1)],1)},m=[],h=s(1001),p={},g=(0,h.Z)(p,u,m,!1,null,null,null),v=g.exports,f=s(6878),w=f.Z.extend({render(t){return t("transition",{props:{name:this.computedTransition},on:{beforeEnter:this.onBeforeTransition,afterEnter:this.onAfterTransition,enterCancelled:this.onTransitionCancelled,beforeLeave:this.onBeforeTransition,afterLeave:this.onAfterTransition,leaveCancelled:this.onTransitionCancelled,enter:this.onEnter}},[this.genWindowItem()])}}),y=s(6998),b=s(1050),C=(s(7658),s(1361)),k=C.Z.extend({data(){return{items:[]}},methods:{register(t){const e=this.items[this.internalIndex];this.items.push(t),this.items.sort(((t,e)=>{const s=this.$slots.default.findIndex((e=>t.$vnode.key===e.key)),i=this.$slots.default.findIndex((t=>e.$vnode.key===t.key));return s-i})),t.$on("change",(()=>this.onClick(t))),this.mandatory&&!this.selectedValues.length&&this.updateMandatory(),this.updateItem(t,this.items.indexOf(t)),void 0!==e&&this.updateInternalValue(this.items.indexOf(e))},unregister(t){const e=this.items[this.internalIndex];this.constructor.super.options.methods.unregister.call(this,t),void 0!==e&&this.updateInternalValue(this.items.indexOf(e))}}}),x=y.Z.extend({methods:{genBar(t,e){const s={style:{height:(0,b.kb)(this.height)},props:{activeClass:this.activeClass,centerActive:this.centerActive,dark:this.dark,light:this.light,mandatory:!this.optional,mobileBreakpoint:this.mobileBreakpoint,nextIcon:this.nextIcon,prevIcon:this.prevIcon,showArrows:this.showArrows,value:this.internalValue},on:{"call:slider":this.callSlider,change:t=>{this.internalValue=t}},ref:"items"};return this.setTextColor(this.computedColor,s),this.setBackgroundColor(this.backgroundColor,s),this.$createElement(k,s,[this.genSlider(e),t])}}}),S=s(6052),O=S.Z.extend({data(){return{items:[]}},methods:{register(t){const e=this.items[this.internalIndex];this.items.push(t),this.items.sort(((t,e)=>{const s=this.$slots.default.findIndex((e=>t.$vnode.key===e.key)),i=this.$slots.default.findIndex((t=>e.$vnode.key===t.key));return s-i})),t.$on("change",(()=>this.onClick(t))),this.mandatory&&!this.selectedValues.length&&this.updateMandatory(),this.updateItem(t,this.items.indexOf(t)),void 0!==e&&this.items.indexOf(e)!==this.internalValue&&this.updateInternalValue(this.items.indexOf(e))},unregister(t){const e=this.items[this.internalIndex];this.constructor.super.options.methods.unregister.call(this,t),void 0!==e&&this.updateInternalValue(this.items.indexOf(e))},updateReverse(t,e){const s=this.items.length,i=s-1;return s<=2?t":">"};return t.replace(/[&"'<>]/g,(t=>e[t]))}static getWindowFeatures(){const t=650,e=window.screen.height>=800?800:window.screen.height-100,s=(window.screen.height-e)/2,i=(window.screen.width-t)/2;return`toolbar=0,status=0,top=${s},left=${i},width=${t},height=${e},modal=yes,alwaysRaised=yes`}static hasActiveElementClass(t){return null!==document.activeElement&&document.activeElement.classList.contains(t)}static isFirefox(){return/Firefox/i.test(navigator.userAgent)}static isMobileDevice(){return/iPhone|iPad|iPod|Macintosh|Android|Mobile/i.test(navigator.userAgent)&&"ontouchend"in document}static isSmartphoneHorizontal(){return window.matchMedia("(max-width: 1000px) and (max-height: 450px)").matches}static isSmartphoneVertical(){return window.matchMedia("(max-width: 600px) and (min-height: 450.01px)").matches}static isTabletHorizontal(){return window.matchMedia("(max-width: 1264px) and (max-height: 850px)").matches}static isTabletVertical(){return window.matchMedia("(max-width: 850px) and (min-height: 850.01px)").matches}static isTouchDevice(){return window.matchMedia("(hover: none)").matches}static mathFloor(t,e=0){return Math.floor(t*10**e)/10**e}static async sleep(t){return await new Promise((e=>setTimeout(e,1e3*t)))}static time(){return Date.now()/1e3}static typeof(t){return Object.prototype.toString.call(t).slice(8,-1).toLowerCase()}static URLtoLink(t){t=$.escapeHTML(t);const e=/(https?:\/\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/gi;return t.replace(e,'$1')}}$.version="0.7.1",$.api_base_url=(()=>`${window.location.protocol}//${window.location.host}/api`)();var A=$;class D{static getChannelType(t){try{const e=t.match("(?[a-z]+)[0-9]+").groups.channel_type.toUpperCase();return e}catch(e){return"GR"}}static getChannelForceType(t){return null===t?"normal":t>=500?"festival":t>=200?"so-many":t>=100?"many":"normal"}}var B=s(8764),N=s(9204),K={success(t){var e;return null===(e=window.KonomiTVVueInstance)||void 0===e?void 0:e.$message.success(t)},info(t){var e;return null===(e=window.KonomiTVVueInstance)||void 0===e?void 0:e.$message.info(t)},warning(t){var e;return null===(e=window.KonomiTVVueInstance)||void 0===e?void 0:e.$message.warning(t)},error(t){var e;return null===(e=window.KonomiTVVueInstance)||void 0===e?void 0:e.$message.error(t)},show(t){var e;return null===(e=window.KonomiTVVueInstance)||void 0===e?void 0:e.$message.show(t)}},L=s(6154);const E=L["default"].create();E.interceptors.request.use((t=>{var e;if(void 0===t.baseURL&&(t.baseURL=ht.api_base_url),!1===(null===(e=t.url)||void 0===e?void 0:e.startsWith("http"))){const e=ht.getAccessToken();null!==e&&(t.headers["Authorization"]=`Bearer ${e}`),t.headers["X-KonomiTV-Version"]=ht.version}return t.timeout=3e4,t}));var H=E;class M{static async createUser(t){const e=await G.post("/users",t);if("is_error"in e){switch(e.error.message){case"Specified username is duplicated":K.error("ユーザー名が重複しています。");break;case"Specified username is not accepted due to system limitations":K.error("ユーザー名に token と me は使えません。");break;default:G.showGenericError(e,"アカウントを作成できませんでした。");break}return null}return e.data}static async createUserAccessToken(t,e){const s=await G.post("/users/token",new URLSearchParams({username:t,password:e}));if("is_error"in s){switch(s.error.message){case"Incorrect username":K.error("ログインできませんでした。そのユーザー名のアカウントは存在しません。");break;case"Incorrect password":K.error("ログインできませんでした。パスワードを間違えていませんか?");break;default:G.showGenericError(s,"ログインできませんでした。");break}return null}return s.data}static async fetchUser(){const t=await G.get("/users/me");return"is_error"in t?(G.showGenericError(t,"アカウント情報を取得できませんでした。"),null):t.data}static async fetchUserIcon(){const t=await G.get("/users/me/icon",{responseType:"blob"});return"is_error"in t?(G.showGenericError(t,"アイコン画像を取得できませんでした。"),null):URL.createObjectURL(t.data)}static async updateUser(t){const e=await G.put("/users/me",t);if("is_error"in e)switch(e.error.message){case"Specified username is duplicated":K.error("ユーザー名が重複しています。");break;case"Specified username is not accepted due to system limitations":K.error("ユーザー名に token と me は使えません。");break;default:G.showGenericError(e,"アカウント情報を更新できませんでした。");break}else;}static async updateUserIcon(t){const e=new FormData;e.append("image",t);const s=await G.put("/users/me/icon",e,{headers:{"Content-Type":"multipart/form-data"}});if("is_error"in s)switch(s.error.message){case"Please upload JPEG or PNG image":K.error("JPEG または PNG 画像をアップロードしてください。");break;default:G.showGenericError(s,"アイコン画像を更新できませんでした。");break}else;}static async deleteUser(){const t=await G["delete"]("/users/me");"is_error"in t&&G.showGenericError(t,"アカウントを削除できませんでした。")}}var U=M;const V=(0,a.Q_)("user",{state:()=>({is_logged_in:!1,user:null,user_icon_url:null}),getters:{user_niconico_icon_url(){if(null===this.user||null===this.user.niconico_user_id)return null;const t=this.user.niconico_user_id.toString().slice(0,4);return`https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/${t}/${this.user.niconico_user_id}.jpg`}},actions:{async register(t,e){const s=await U.createUser({username:t,password:e});return null===s?(console.log("Register failed."),!1):(await this.login(t,e,!0),console.log("Register successful."),K.success("アカウントを作成しました。"),!0)},async login(t,e,s=!1){const i=await U.createUserAccessToken(t,e);return null===i?(console.log("Login failed."),this.logout(!0),!1):(console.log("Login successful."),ht.saveAccessToken(i.access_token),this.is_logged_in=!0,await this.fetchUser(!0),!1===s&&K.success("ログインしました。"),!0)},logout(t=!1){const e=st();e.settings.sync_settings=!1,ht.deleteAccessToken(),this.is_logged_in=!1,this.user=null,this.user_icon_url="",!1===t&&K.success("ログアウトしました。")},async fetchUser(t=!1){if(null===ht.getAccessToken())return null;if(null!==this.user&&!1===t)return this.user;const e=await U.fetchUser();if(null===e)return null===ht.getAccessToken()&&this.logout(!0),null;this.is_logged_in=!0,this.user=e;const s=await U.fetchUserIcon();return null===s?null:(this.user_icon_url=s,this.user)},async updateUser(t){await U.updateUser(t),await this.fetchUser(!0),void 0!==t.username?K.show("ユーザー名を更新しました。"):void 0!==t.password&&K.show("パスワードを更新しました。")},async updateUserIcon(t){await U.updateUserIcon(t),await this.fetchUser(!0),K.show("アイコン画像を更新しました。")},async deleteUser(){await U.deleteUser(),this.logout(!0),K.show("アカウントを削除しました。")}}});var R=V;class F{static async request(t){const e=await H.request(t).catch((t=>t));return e instanceof N.d7?(console.error(e),e.response?{status:e.response.status,data:null,error:new Error(e.response.data.detail),is_error:!0}:{status:NaN,data:null,error:e,is_error:!0}):{status:e.status,data:e.data,error:null,is_success:!0}}static async get(t,e){const s=Object.assign({url:t,method:"GET"},e);return await F.request(s)}static async post(t,e,s){const i=Object.assign({url:t,method:"POST",data:e},s);return await F.request(i)}static async put(t,e,s){const i=Object.assign({url:t,method:"PUT",data:e},s);return await F.request(i)}static async delete(t,e){const s=Object.assign({url:t,method:"DELETE"},e);return await F.request(s)}static showGenericError(t,e){const s=R();switch(t.error.message){case"Not authenticated":return s.logout(!0),void K.error(`${e}\nログインし直してください。`);case"Access token data is invalid":return s.logout(!0),void K.error(`${e}\nログインセッションが不正です。もう一度ログインし直してください。`);case"Access token is invalid":return s.logout(!0),void K.error(`${e}\nログインセッションの有効期限が切れています。もう一度ログインし直してください。`);case"User associated with access token does not exist":return s.logout(!0),void K.error(`${e}\nログインセッションに紐づくユーザーが存在しないか、削除されています。`);default:return void(t.error.message?Number.isNaN(t.status)?K.error(`${e}(${t.error.message})`):K.error(`${e}(HTTP Error ${t.status} / ${t.error.message})`):K.error(`${e}(HTTP Error ${t.status})`))}}}var G=F;class W{static async fetchClientSettings(){const t=await G.get("/settings/client");return"is_error"in t?null:t.data}static async updateClientSettings(t){await G.put("/settings/client",t)}}var q=W;const X=["pinned_channel_ids","saved_twitter_hashtags","panel_display_state","tv_panel_active_tab","tv_channel_selection_requires_alt_key","caption_font","always_border_caption_text","specify_caption_opacity","caption_opacity","tv_show_superimpose","capture_save_mode","capture_caption_mode","comment_speed_rate","comment_font_size","close_comment_form_after_sending","muted_comment_keywords","muted_niconico_user_ids","mute_vulgar_comments","mute_abusive_discriminatory_prejudiced_comments","mute_big_size_comments","mute_fixed_comments","mute_colored_comments","mute_consecutive_same_characters_comments","fold_panel_after_sending_tweet","reset_hashtag_when_program_switches","auto_add_watching_channel_hashtag","twitter_active_tab","tweet_hashtag_position","tweet_capture_watermark_position"],Y={pinned_channel_ids:[],showed_panel_last_time:!0,selected_twitter_account_id:null,saved_twitter_hashtags:[],tv_streaming_quality:"1080p",tv_data_saver_mode:!1,tv_low_latency_mode:!0,panel_display_state:"RestorePreviousState",tv_panel_active_tab:"Program",tv_channel_selection_requires_alt_key:!1,caption_font:"Windows TV MaruGothic",always_border_caption_text:!0,specify_caption_opacity:!1,caption_opacity:.5,tv_show_superimpose:!0,capture_copy_to_clipboard:!1,capture_save_mode:"UploadServer",capture_caption_mode:"Both",sync_settings:!1,comment_speed_rate:1,comment_font_size:34,close_comment_form_after_sending:!0,muted_comment_keywords:[],muted_niconico_user_ids:[],mute_vulgar_comments:!0,mute_abusive_discriminatory_prejudiced_comments:!0,mute_big_size_comments:!0,mute_fixed_comments:!1,mute_colored_comments:!1,mute_consecutive_same_characters_comments:!1,fold_panel_after_sending_tweet:!1,reset_hashtag_when_program_switches:!0,auto_add_watching_channel_hashtag:!0,twitter_active_tab:"Capture",tweet_hashtag_position:"Append",tweet_capture_watermark_position:"None"};function J(){const t=localStorage.getItem("KonomiTV-Settings");return null!==t?JSON.parse(t):(Q(Y),Y)}function Q(t){localStorage.setItem("KonomiTV-Settings",JSON.stringify(t))}function tt(t){const e={};for(const s of Object.keys(Y))e[s]=s in t?t[s]:Y[s];return e}const et=(0,a.Q_)("settings",{state:()=>{const t=J(),e=tt(t);return Q(e),{settings:e}},actions:{async importClientSettings(t){const e=await t.text();let s={};try{s=JSON.parse(e)}catch(a){return!1}const i=tt(s);return Q(i),this.settings=i,await this.syncClientSettingsToServer(),!0},async resetClientSettings(){const t=Object.assign(Object.assign({},Y),{sync_settings:this.settings.sync_settings});Q(t),this.settings=t,await this.syncClientSettingsToServer()},getSyncableClientSettings(){const t={};for(const e of X)e in this.settings?t[e]=this.settings[e]:t[e]=Y[e];return t},async syncClientSettingsFromServer(t=!1){if(null===ht.getAccessToken()||!1===this.settings.sync_settings&&!1===t)return;const e=await q.fetchClientSettings();if(null!==e)for(const[s,i]of Object.entries(e))this.settings[s]=i},async syncClientSettingsToServer(t=!1){if(null===ht.getAccessToken()||!1===this.settings.sync_settings&&!1===t)return;const e=this.getSyncableClientSettings();await q.updateClientSettings(e)}}});var st=et;class it{static getCommentColor(t){return this.color_table[t]||null}static getCommentPosition(t){switch(t){case"ue":return"top";case"naka":return"right";case"shita":return"bottom";default:return null}}static getCommentSize(t){switch(t){case"big":case"medium":case"small":return t;default:return null}}static parseCommentCommand(t){let e="#FFEAEA",s="right",i="medium";if(void 0!==t&&null!==t){const a=t.replace("184","").split(" ");for(const t of a){const a=it.getCommentColor(t),n=it.getCommentPosition(t),r=it.getCommentSize(t);null!==a&&(e=a),null!==n&&(s=n),null!==r&&(i=r)}}return{color:e,position:s,size:i}}static isMutedComment(t,e,s,i,a){const n=st();if(n.settings.muted_niconico_user_ids.includes(e))return!0;if(it.special_command_comments_pattern.test(t))return!0;if(!0===n.settings.mute_fixed_comments&&("top"===i||"bottom"===i))return console.log("[CommentUtils] Muted comment (fixed_comments): "+t),!0;if(!0===n.settings.mute_colored_comments&&"#FFEAEA"!==s)return console.log("[CommentUtils] Muted comment (colored_comments): "+t),!0;if(!0===n.settings.mute_big_size_comments&&"big"===a)return console.log("[CommentUtils] Muted comment (big_size_comments): "+t),!0;if(!0===n.settings.mute_vulgar_comments&&it.mute_vulgar_comments_pattern.test(t))return console.log("[CommentUtils] Muted comment (vulgar_comments): "+t),!0;if(!0===n.settings.mute_abusive_discriminatory_prejudiced_comments&&it.mute_abusive_discriminatory_prejudiced_comments_pattern.test(t))return console.log("[CommentUtils] Muted comment (abusive_discriminatory_prejudiced_comments): "+t),!0;if(!0===n.settings.mute_consecutive_same_characters_comments&&it.mute_consecutive_same_characters_comments_pattern.test(t))return console.log("[CommentUtils] Muted comment (consecutive_same_characters_comments): "+t),!0;for(const r of n.settings.muted_comment_keywords)if(""!==r.pattern)switch(r.match){case"partial":if(t.includes(r.pattern))return console.log("[CommentUtils] Muted comment (partial): "+t),!0;break;case"forward":if(t.startsWith(r.pattern))return console.log("[CommentUtils] Muted comment (forward): "+t),!0;break;case"backward":if(t.endsWith(r.pattern))return console.log("[CommentUtils] Muted comment (backward): "+t),!0;break;case"exact":if(t===r.pattern)return console.log("[CommentUtils] Muted comment (exact): "+t),!0;break;case"regex":if(new RegExp(r.pattern).test(t))return console.log("[CommentUtils] Muted comment (regex): "+t),!0;break}return!!it.annoying_statistical_comments_pattern.test(t)}static addMutedKeywords(t){const e=st();for(const s of e.settings.muted_comment_keywords)if("exact"===s.match&&s.pattern===t)return;e.settings.muted_comment_keywords.push({match:"exact",pattern:t})}static addMutedNiconicoUserIDs(t){const e=st();e.settings.muted_niconico_user_ids.includes(t)||e.settings.muted_niconico_user_ids.push(t)}}it.mute_vulgar_comments_pattern=new RegExp(B.lW.from("XChpXCl8XChVXCl8cHJwcnzvvZDvvZLvvZDvvZJ8U0VYfFPjgIdYfFPil69YfFPil4tYfFPil49YfO+8s++8pe+8uHzvvLPjgIfvvLh877yz4pev77y4fO+8s+KXi++8uHzvvLPil4/vvLh844Ki44OA44Or44OIfOOCouODiuOCpXzjgqLjg4rjg6t844Kk44Kr6IetfOOCpOOBj3zjgYbjgpPjgZN844Km44Oz44KzfOOBhuOCk+OBoXzjgqbjg7Pjg4F844Ko44Kt44ObfOOBiOOBoeOBiOOBoXzjgYjjgaPjgaF844Ko44OD44OBfOOBiOOBo+OCjXzjgqjjg4Pjg61844GI44KNfOOCqOODrXzlt6Xlj6N844GK44GV44KP44KK44G+44KTfOOBiuOBl+OBo+OBk3zjgqrjgrfjg4PjgrN844Kq44OD44K144OzfOOBiuOBo+OBseOBhHzjgqrjg4Pjg5HjgqR844Kq44OK44OL44O8fOOBiuOBquOBu3zjgqrjg4rjg5t844GK44Gx44GEfOOCquODkeOCpHzjgYpwfOOBiu+9kHzjgqrjg5Xjg5HjgrN844Ks44Kk44K444OzfOOCreODs+OCv+ODnnzjgY/jgbHjgYJ844GP44Gx44GBfOOCr+ODquODiOODquOCuXzjgq/jg7Pjg4t844GU44GP44GU44GP44GU44GP44GU44GPfOOCs+ODs+ODieODvOODoHzjgZHjgaTjgYLjgap844Kx44OE44Ki44OKfOOCtuODvOODoeODs3zjgrfjgrN844GX44GT44GX44GTfOOCt+OCs+OCt+OCs3zjgZnjgZHjgZnjgZF844Gb44GE44GI44GNfOOBm+OBhOOCinzjgZvjg7zjgop844GZ44GF44GF44GF44GF44GFfOOBmeOBhuOBhuOBhuOBhuOBhnzjgrvjgq/jg63jgrl844K744OD44Kv44K5fOOCu+ODleODrHzjgaHjgaPjgbHjgYR844Gh44Gj44OR44KkfOODgeODg+ODkeOCpHzjgaHjgpPjgZN844Gh44CH44GTfOOBoeKXr+OBk3zjgaHil4vjgZN844Gh4peP44GTfOODgeODs+OCs3zjg4HjgIfjgrN844OB4pev44KzfOODgeKXi+OCs3zjg4Hil4/jgrN844Gh44KT44G9fOOBoeOAh+OBvXzjgaHil6/jgb1844Gh4peL44G9fOOBoeKXj+OBvXzjg4Hjg7Pjg51844OB44CH44OdfOODgeKXr+ODnXzjg4Hil4vjg51844OB4peP44OdfOOBoeOCk+OBoeOCk3zjg4Hjg7Pjg4Hjg7N844Gm44GD44KT44Gm44GD44KTfOODhuOCo+ODs+ODhuOCo+ODs3zjg4bjgqPjg7Pjg51844OH44Kr44GEfOODh+ODquODmOODq3zjgarjgYvjgaDjgZd844Gq44GL44CH44GXfOOBquOBi+KXr+OBl3zjgarjgYvil4vjgZd844Gq44GL4peP44GXfOiEseOBknzjg4zjgYR844OM44GLfOODjOOCq3zjg4zjgY1844OM44KtfOODjOOBj3zjg4zjgq9844OM44GRfOODjOOCsXzjg4zjgZN844OM44KzfOOBseOBhOOCguOBv3zjg5Hjg5HmtLt844G144GG44O7fOOBteOBhuKApnzjgbXjgYV8776M772pfOOBteOBj+OCieOBv3zjgbXjgY/jgonjgpPjgad844G644Gj44GffOOBuuOCjeOBuuOCjXzjg5rjg63jg5rjg618776N776f776b776N776f776bfOODleOCp+ODqXzjgbvjgYbjgZHjgYR844G844Gj44GNfOODneODq+ODjnzjgbzjgo3jgpN844Oc44Ot44OzfO++ju++nu++m+++nXzjgb3jgo3jgop844Od44Ot44OqfO++ju++n+++m+++mHzjg57jg7PjgY3jgaR844Oe44Oz44Kt44OEfOOBvuOCk+OBk3zjgb7jgIfjgZN844G+4pev44GTfOOBvuKXi+OBk3zjgb7il4/jgZN844Oe44Oz44KzfOODnuOAh+OCs3zjg57il6/jgrN844Oe4peL44KzfOODnuKXj+OCs3zjgb7jgpPjgZXjgpN844KC44Gj44GT44KKfOODouODg+OCs+ODqnzjgoLjgb/jgoLjgb9844Oi44Of44Oi44OffOODpOOBo+OBn3zjg6TjgaPjgaZ844Ok44KJfOOChOOCieOBm+OCjXzjg6Tjgop844Ok44KLfOODpOOCjHzjg6Tjgo1844Op44OW44ObfOODr+ODrOODoXzmhJvmtrJ85ZaYfOmZsOaguHzpmbDojI586Zmw5ZSHfOa3q+WkonzpmqDmr5t86Zmw5q+bfOeUo+OCgeOCi3zlpbPjga7lrZDjga7ml6V85rGa44Gj44GV44KTfOWnpnzpqI7kuZfkvY185beo5qC5fOW3qOODgeODs3zlt6jnj4186YeR546JfOaciOe1jHzlvozog4zkvY185a2Q56iufOWtkOS9nOOCinzlsITnsr585L+h6ICFfOeyvua2snzpgI/jgZF85oCn5LqkfOeyvuWtkHzmraPluLjkvY185oCn5b60fOaAp+eahHznlJ/nkIZ85a+45q2i44KBfOe0oOadkHzmirHjgYR85oqx44GLfOaKseOBjXzmirHjgY985oqx44GRfOaKseOBk3zkvZPmtrJ85Lmz6aaWfOaBpeWeonznj43mo5J85Lit44Gg44GXfOS4reWHuuOBl3zlsL985oqc44GEfOaKnOOBkeOBquOBhHzmipzjgZHjgot85oqc44GR44KMfOeKr+ePjXzohqjjgol85YyF6IyOfOWLg+i1t3zmkannvoV86a2U576FfOaPieOBvnzmj4njgb985o+J44KAfOaPieOCgXzmvKvmuZZ844CH772efOKXr++9nnzil4vvvZ584peP772efOOAh+ODg+OCr+OCuXzil6/jg4Pjgq/jgrl84peL44OD44Kv44K5fOKXj+ODg+OCr+OCuQ==","base64").toString()),it.mute_abusive_discriminatory_prejudiced_comments_pattern=new RegExp(B.lW.from("44CCfOOCouODi+ODl+ODrOOBj+OCk3zjgqLjg4vjg5fjg6zlkJt844Ki44K544OafOOCpOOCq+OCjHzjgYTjgb7jgYTjgaF844Kk44Oe44Kk44OBfOOCpOODqeOBpOOBj3zjgqbjgrh844Km44O844OofOOCpuODqHzjgqbjg6jjgq9844Km44OyfOOBjeOCguOBhHzjgq3jg6LjgqR844Kt44Oi44GEfOOCrS/jg6Av44OBfOOCrOOCpOOCuHzvvbbvvp7vvbLvvbzvvp5844Ks44KtfOOCq+OCuXzjgq3jg4Pjgrp844GN44Gh44GM44GEfOOCreODgeOCrOOCpHzjgq3jg6Djg4F844K344OKfOOCueODhuODnnzjgaTjgb7jgonjgap844Gk44G+44KJ44KTfOODgeODp+ODg+ODkeODqnzjg4Hjg6fjg7N85Y2D44On44OzfOOBpOOCk+OBvHzjg4Tjg7Pjg5x844ON44OI44Km44OofOOBq+OBoOOBguOBgnzjg4vjg4B85LqM44OAfO++hu++gO++nnzjg5Hjg7zjg6h844OR44OofOODkeODqOOCr3zjgbbjgaPjgZV844OW44OD44K1fOOBtuOBleOBhHzjg5bjgrXjgqR844G+44Gs44GRfOODoeOCr+ODqXzjg5Djgqt844Og44Kr44Gk44GPfOiNkuOCieOBl3zpurvnlJ/jgrvjg6Hjg7Pjg4h85oWw5a6J5ammfOWus+WFkHzlpJblrZd85aem5Zu9fOmfk+WbvXzpn5PkuK186Z+T5pelfOWfuuWcsOWklnzmsJfni4LjgYR85rCX6YGV44GEfOWIh+OBo+OBn3zliIfjgaPjgaZ85rCX5oyB44Gh5oKqfOWbveS6pOaWree1tnzmrrp86aCDfOmgg+OBl3zpoIPjgZl86aCD44GbfOWcqOaXpXzlj4LmlL/mqKl85q2744GtfOawj+OBrXzvvoDvvot85q255YyVfOatueODknzlpLHpgJ986Zqc5a6zfOaWreS6pHzkuK3pn5N85pyd6a6ufOW+tOeUqOW3pXzlo7p85aO3fOWjvHzml6Xpn5N85pel5bidfOeymOedgHzlj43ml6V86aas6bm/fOeZuueLgnznmbrpgZR85py0fOWjsuWbvXzkuI3lv6t85L215ZCIfOmWk+aKnOOBkXzmloflj6V86Z2W5Zu9","base64").toString()),it.mute_consecutive_same_characters_comments_pattern=/(.)\1{7,}/,it.special_command_comments_pattern=/\/[a-z]+ /,it.annoying_statistical_comments_pattern=/最高\d+米\/|計\d+ID|総\d+米/,it.color_table={white:"#FFEAEA",red:"#F02840",pink:"#FD7E80",orange:"#FDA708",yellow:"#FFE133",green:"#64DD17",cyan:"#00D4F5",blue:"#4763FF",purple:"#D500F9",black:"#1E1310",white2:"#CCCC99",niconicowhite:"#CCCC99",red2:"#CC0033",truered:"#CC0033",pink2:"#FF33CC",orange2:"#FF6600",passionorange:"#FF6600",yellow2:"#999900",madyellow:"#999900",green2:"#00CC66",elementalgreen:"#00CC66",cyan2:"#00CCCC",blue2:"#3399FF",marineblue:"#3399FF",purple2:"#6633CC",nobleviolet:"#6633CC",black2:"#666666"};class at{static generatePlayerBackgroundURL(){const t=50,e=Math.floor(Math.random()*t)+1;return`/assets/images/player-backgrounds/${e.toString().padStart(2,"0")}.jpg`}static isHEVCVideoSupported(){return"probably"===document.createElement("video").canPlayType('video/mp4; codecs="hvc1.1.6.L123.B0"')}}var nt=s(7484),rt=s.n(nt),ot=(s(6831),s(6607)),lt=s.n(ot),ct=s(9212),_t=s.n(ct),dt=s(7412),ut=s.n(dt);class mt{static decorateProgramInfo(t,e){if(null!==t&&null!==t[e]){const s=ht.escapeHTML(t[e]),i="新|終|再|交|映|手|声|多|副|字|文|CC|OP|二|S|B|SS|無|無料C|S1|S2|S3|MV|双|デ|D|N|W|P|H|HV|SD|天|解|料|前|後初|生|販|吹|PPV|演|移|他|収|・|英|韓|中|字/日|字/日英|3D|2K|4K|8K|5.1|7.1|22.2|60P|120P|d|HC|HDR|SHV|UHD|VOD|配|初",a=new RegExp("\\((二|字|再)\\)","g"),n=new RegExp(`\\[(${i})\\]`,"g");return s.replace(a,'$1').replace(n,'$1')}{rt().extend(_t()),rt().extend(ut()),rt().extend(lt());const t=rt()(),s=rt()().hour(0).minute(0).second(0),i=rt()().hour(6).minute(59).second(59),a=rt()().hour(23).minute(0).second(0),n=rt()().hour(23).minute(59).second(59);return t.isSameOrAfter(s)&&t.isSameOrBefore(i)||t.isSameOrAfter(a)&&t.isSameOrBefore(n)?"title"===e?"放送休止":"この時間は放送を休止しています。":"title"===e?"番組情報がありません":"この時間の番組情報を取得できませんでした。"}}static getAttribute(t,e,s){return null!==t&&void 0!==t[e]&&null!==t[e]?t[e]:s}static getProgramProgress(t){if(null!==t){const e=rt()(rt()()).diff(t.start_time,"second");return e/t.duration*100}return 0}static getProgramTime(t,e=!1){if(null!==t&&"2000-01-01T00:00:00+09:00"!==t.start_time){rt().locale("ja");const s=rt()(t.start_time),i=rt()(t.end_time),a=t.duration/60;return!0===e?`${s.format("HH:mm")} ~ ${i.format("HH:mm")}`:`${s.format("YYYY/MM/DD (dd) HH:mm")} ~ ${i.format("HH:mm")} (${a}分)`}return!0===e?"--:-- ~ --:--":"----/--/-- (-) --:-- ~ --:-- (--分)"}}var ht=A,pt=s(3058),gt=s(5223),vt=s(4192),ft=s(6904),wt=s(2469),yt=s(5251),bt=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"route-container"},[e("Header"),e("main",[e("Navigation"),e("div",{staticClass:"login-container-wrapper d-flex align-center w-100 mb-13"},[e(pt.Z,{staticClass:"login-container px-10 pt-8 pb-11 mx-auto background lighten-1",attrs:{elevation:"10",width:"100%","max-width":"450"}},[e(gt.EB,{staticClass:"login__logo flex-column justify-center"},[e(wt.Z,{attrs:{"max-width":"250",src:"/assets/images/logo.svg"}}),e("h4",{staticClass:"mt-10"},[t._v("ログイン")])],1),e(vt.Z),e(ft.Z,{ref:"login",on:{submit:function(t){t.preventDefault()}}},[e(yt.Z,{staticClass:"mt-12",attrs:{outlined:"",placeholder:"ユーザー名","hide-details":"",autofocus:"",dense:t.is_form_dense},model:{value:t.username,callback:function(e){t.username=e},expression:"username"}}),e(yt.Z,{staticClass:"mt-8",attrs:{outlined:"",placeholder:"パスワード","hide-details":"",dense:t.is_form_dense,type:t.password_showing?"text":"password","append-icon":t.password_showing?"mdi-eye":"mdi-eye-off"},on:{"click:append":function(e){t.password_showing=!t.password_showing}},model:{value:t.password,callback:function(e){t.password=e},expression:"password"}}),e(j.Z,{staticClass:"login-button mt-5",attrs:{color:"secondary",depressed:"",width:"100%",height:"56"},on:{click:function(e){return t.login()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fa:sign-in"}}),t._v("ログイン ")],1)],1)],1)],1)],1)],1)},Ct=[],kt=s(3347),xt=s(3176),St=function(){var t=this,e=t._self._c;return e("header",{staticClass:"header"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"konomitv-logo ml-3 ml-md-6",attrs:{to:"/tv/"}},[e("img",{staticClass:"konomitv-logo__image",attrs:{src:"/assets/images/logo.svg",height:"21"}})]),e(kt.Z)],1)},Ot=[],Tt={},jt=(0,h.Z)(Tt,St,Ot,!1,null,"84897154",null),It=jt.exports,Pt=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",[e("div",{staticClass:"navigation-container elevation-8"},[e("nav",{staticClass:"navigation"},[e("div",{staticClass:"navigation-scroll"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"navigation__link",attrs:{"active-class":"navigation__link--active",to:"/tv/"}},[e("Icon",{staticClass:"navigation__link-icon",attrs:{icon:"fluent:tv-20-regular",width:"26px"}}),e("span",{staticClass:"navigation__link-text"},[t._v("テレビをみる")])],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"navigation__link",attrs:{"active-class":"navigation__link--active",to:"/videos/"}},[e("Icon",{staticClass:"navigation__link-icon",attrs:{icon:"fluent:movies-and-tv-20-regular",width:"26px"}}),e("span",{staticClass:"navigation__link-text"},[t._v("ビデオをみる")])],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"navigation__link",attrs:{"active-class":"navigation__link--active",to:"/timetable/"}},[e("Icon",{staticClass:"navigation__link-icon",attrs:{icon:"fluent:calendar-ltr-20-regular",width:"26px"}}),e("span",{staticClass:"navigation__link-text"},[t._v("番組表")])],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"navigation__link",attrs:{"active-class":"navigation__link--active",to:"/reserves/"}},[e("Icon",{staticClass:"navigation__link-icon",staticStyle:{padding:"0.5px"},attrs:{icon:"fluent:timer-16-regular",width:"26px"}}),e("span",{staticClass:"navigation__link-text"},[t._v("録画予約")])],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"navigation__link",attrs:{"active-class":"navigation__link--active",to:"/mylist/"}},[e("Icon",{staticClass:"navigation__link-icon",attrs:{icon:"ic:round-playlist-play",width:"26px"}}),e("span",{staticClass:"navigation__link-text"},[t._v("マイリスト")])],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"navigation__link",attrs:{"active-class":"navigation__link--active",to:"/captures/"}},[e("Icon",{staticClass:"navigation__link-icon",attrs:{icon:"fluent:image-multiple-24-regular",width:"26px"}}),e("span",{staticClass:"navigation__link-text"},[t._v("キャプチャ")])],1),e(kt.Z),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"navigation__link",attrs:{"active-class":"navigation__link--active",to:"/settings/"}},[e("Icon",{staticClass:"navigation__link-icon",attrs:{icon:"fluent:settings-20-regular",width:"26px"}}),e("span",{staticClass:"navigation__link-text"},[t._v("設定")])],1),e("a",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.top",value:t.versionStore.is_update_available?`アップデートがあります (version ${t.versionStore.latest_version})`:"",expression:"versionStore.is_update_available ?\n `アップデートがあります (version ${versionStore.latest_version})` : ''",modifiers:{top:!0}}],staticClass:"navigation__link",class:{"navigation__link--version":t.versionStore.is_client_develop_version,"navigation__link--highlight":t.versionStore.is_update_available},attrs:{"active-class":"navigation__link--active",href:"https://github.com/tsukumijima/KonomiTV"}},[e("Icon",{staticClass:"navigation__link-icon",attrs:{icon:"fluent:info-16-regular",width:"26px"}}),e("span",{staticClass:"navigation__link-text"},[t._v("version "+t._s(t.versionStore.client_version))])],1)],1)])]),e("BottomNavigation")],1)},Zt=[],zt=s(1249),$t=function(){var t=this,e=t._self._c;return e(zt.Z,{staticClass:"bottom-navigation-container elevation-12",attrs:{color:"primary",grow:""}},[e(j.Z,{staticClass:"bottom-navigation-button",attrs:{to:"/tv/"}},[e("span",{staticClass:"mt-1"},[t._v("テレビをみる")]),e("Icon",{attrs:{icon:"fluent:tv-20-regular",width:"30px"}})],1),e(j.Z,{staticClass:"bottom-navigation-button",attrs:{to:"/videos/"}},[e("span",{staticClass:"mt-1"},[t._v("ビデオをみる")]),e("Icon",{attrs:{icon:"fluent:movies-and-tv-20-regular",width:"30px"}})],1),e(j.Z,{staticClass:"bottom-navigation-button",attrs:{to:"/settings/"}},[e("span",{staticClass:"mt-1"},[t._v("設定")]),e("Icon",{attrs:{icon:"fluent:settings-20-regular",width:"30px"}})],1)],1)},At=[],Dt={},Bt=(0,h.Z)(Dt,$t,At,!1,null,"3df53df3",null),Nt=Bt.exports;class Kt{static async fetchServerVersion(){const t=await G.get("/version");return"is_error"in t?(G.showGenericError(t,"バージョン情報を取得できませんでした。"),null):t.data}}var Lt=Kt;const Et=(0,a.Q_)("version",{state:()=>({server_version_info:null,last_updated_at:0}),getters:{client_version(){return ht.version},server_version(){var t,e;return null!==(e=null===(t=this.server_version_info)||void 0===t?void 0:t.version)&&void 0!==e?e:null},latest_version(){var t,e;return null!==(e=null===(t=this.server_version_info)||void 0===t?void 0:t.latest_version)&&void 0!==e?e:null},is_client_develop_version(){return this.client_version.includes("-dev")},is_server_develop_version(){var t,e;return null!==(e=null===(t=this.server_version)||void 0===t?void 0:t.includes("-dev"))&&void 0!==e&&e},is_update_available(){return null!==this.server_version&&null!==this.latest_version&&(!1===this.is_server_develop_version&&this.server_version!==this.latest_version||!0===this.is_server_develop_version&&this.server_version.replace("-dev","")===this.latest_version)},is_version_mismatch(){return null!==this.server_version&&this.client_version!==this.server_version}},actions:{async fetchServerVersion(t=!1){if(null!==this.server_version_info&&!1===t)return ht.time()-this.last_updated_at>60&&this.fetchServerVersion(!0),this.server_version_info;const e=await Lt.fetchServerVersion();return null===e?null:(this.server_version_info=e,this.last_updated_at=ht.time(),this.server_version_info)}}});var Ht=Et,Mt=o["default"].extend({name:"Navigation",components:{BottomNavigation:Nt},computed:Object.assign({},(0,a.Kc)(Ht)),async created(){await this.versionStore.fetchServerVersion()}}),Ut=Mt,Vt=(0,h.Z)(Ut,Pt,Zt,!1,null,"5b40940b",null),Rt=Vt.exports,Ft=o["default"].extend({name:"Login",components:{Header:It,Navigation:Rt},data(){return{is_form_dense:ht.isSmartphoneHorizontal(),username:"",password:"",password_showing:!1}},computed:Object.assign({},(0,a.Kc)(R)),async created(){await this.userStore.fetchUser(),this.userStore.is_logged_in&&await this.$router.replace({path:"/settings/account"})},methods:{async login(){if(""===this.username||""===this.password)return void this.$message.error("ユーザー名またはパスワードが空です。");const t=await this.userStore.login(this.username,this.password);!1!==t&&await this.$router.replace({path:"/settings/account"})}}}),Gt=Ft,Wt=(0,h.Z)(Gt,bt,Ct,!1,null,"851c3dec",null),qt=Wt.exports,Xt=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"route-container"},[e("Header"),e("main",[e("Navigation"),t._m(0)],1)],1)},Yt=[function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"d-flex justify-center align-center w-100"},[e("div",{staticClass:"d-flex justify-center align-center flex-column"},[e("h1",[t._v("Not Found, or Under Development...")]),e("span",{staticClass:"mt-4 text--text text--darken-1"},[t._v("お探しのページは存在しないか、鋭意開発中です。")])])])}],Jt=o["default"].extend({name:"NotFound",components:{Header:It,Navigation:Rt}}),Qt=Jt,te=(0,h.Z)(Qt,Xt,Yt,!1,null,"1310cfee",null),ee=te.exports,se=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"route-container"},[e("Header"),e("main",[e("Navigation"),e("div",{staticClass:"register-container-wrapper d-flex align-center w-100 mb-13"},[e(pt.Z,{staticClass:"register-container px-10 pt-8 pb-11 mx-auto background lighten-1",attrs:{elevation:"10",width:"100%","max-width":"450"}},[e(gt.EB,{staticClass:"register__logo flex-column justify-center"},[e(wt.Z,{attrs:{"max-width":"250",src:"/assets/images/logo.svg"}}),e("h4",{staticClass:"mt-10"},[t._v("アカウントを作成")])],1),e(vt.Z),e(ft.Z,{ref:"register",on:{submit:function(t){t.preventDefault()}}},[e(yt.Z,{staticClass:"mt-12",attrs:{outlined:"",placeholder:"ユーザー名",autofocus:"",dense:t.is_form_dense,rules:[t.username_validation]},model:{value:t.username,callback:function(e){t.username=e},expression:"username"}}),e(yt.Z,{staticStyle:{"margin-top":"2px"},attrs:{outlined:"",placeholder:"パスワード",dense:t.is_form_dense,type:t.password_showing?"text":"password","append-icon":t.password_showing?"mdi-eye":"mdi-eye-off",rules:[t.password_validation]},on:{"click:append":function(e){t.password_showing=!t.password_showing}},model:{value:t.password,callback:function(e){t.password=e},expression:"password"}}),e(j.Z,{staticClass:"register-button mt-5",attrs:{color:"secondary",depressed:"",width:"100%",height:"56"},on:{click:function(e){return t.register()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:person-add-20-filled",height:"24"}}),t._v("アカウントを作成 ")],1)],1)],1)],1)],1)],1)},ie=[],ae=o["default"].extend({name:"Register",components:{Header:It,Navigation:Rt},data(){return{is_form_dense:ht.isSmartphoneHorizontal(),username:null,username_validation:t=>""===t||null===t?"ユーザー名を入力してください。":!1!==/^.{2,}$/.test(t)||"ユーザー名は2文字以上で入力してください。",password:null,password_showing:!0,password_validation:t=>""===t||null===t?"パスワードを入力してください。":!1!==/^[a-zA-Z0-9!-/:-@¥[-`{-~]{4,}$/.test(t)||"パスワードは4文字以上の半角英数記号を入力してください。"}},computed:Object.assign({},(0,a.Kc)(R)),async created(){await this.userStore.fetchUser(),this.userStore.is_logged_in&&await this.$router.replace({path:"/settings/account"})},methods:{async register(){if(!1===this.$refs.register.validate())return;if(null===this.username||null===this.password)return;const t=await this.userStore.register(this.username,this.password);!1!==t&&await this.$router.replace({path:"/settings/account"})}}}),ne=ae,re=(0,h.Z)(ne,se,ie,!1,null,"6533f3d0",null),oe=re.exports,le=s(9789),ce=s(271),_e=s(5787),de=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("SettingsBase",[e("h2",{staticClass:"settings__heading"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"settings__back-button",attrs:{to:"/settings/"}},[e("Icon",{attrs:{icon:"fluent:arrow-left-12-filled",width:"25px"}})],1),e("Icon",{attrs:{icon:"fluent:person-20-filled",width:"25px"}}),e("span",{staticClass:"ml-2"},[t._v("アカウント")])],1),e("div",{staticClass:"settings__content",class:{"settings__content--loading":t.is_loading}},[null===t.userStore.user?e("div",{staticClass:"account"},[e("div",{staticClass:"account-wrapper"},[e("img",{staticClass:"account__icon",attrs:{src:"/assets/images/account-icon-default.png"}}),e("div",{staticClass:"account__info"},[e("div",{staticClass:"account__info-name"},[e("span",{staticClass:"account__info-name-text"},[t._v("ログインしていません")])]),e("span",{staticClass:"account__info-id"},[t._v("Not logged in")])])]),e(j.Z,{staticClass:"account__login ml-auto",attrs:{color:"secondary",width:"140",height:"56",depressed:"",to:"/login/"}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fa:sign-in"}}),t._v("ログイン ")],1)],1):t._e(),null!==t.userStore.user?e("div",{staticClass:"account"},[e("div",{staticClass:"account-wrapper"},[e("img",{staticClass:"account__icon",attrs:{src:t.userStore.user_icon_url}}),e("div",{staticClass:"account__info"},[e("div",{staticClass:"account__info-name"},[e("span",{staticClass:"account__info-name-text"},[t._v(t._s(t.userStore.user.name))]),t.userStore.user.is_admin?e("span",{staticClass:"account__info-admin"},[t._v("管理者")]):t._e()]),e("span",{staticClass:"account__info-id"},[t._v("User ID: "+t._s(t.userStore.user.id))])])]),e(j.Z,{staticClass:"account__login ml-auto",attrs:{color:"secondary",width:"140",height:"56",depressed:""},on:{click:function(e){return t.userStore.logout()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fa:sign-out"}}),t._v("ログアウト ")],1)],1):t._e(),!1===t.userStore.is_logged_in?e("div",{staticClass:"account-register"},[e("div",{staticClass:"account-register__heading"},[t._v(" KonomiTV アカウントにログインすると、"),e("br"),t._v("より便利な機能が使えます! ")]),e("div",{staticClass:"account-register__feature"},[e("div",{staticClass:"account-feature"},[e("Icon",{staticClass:"account-feature__icon",attrs:{icon:"bi:chat-left-text-fill"}}),e("div",{staticClass:"account-feature__info"},[e("span",{staticClass:"account-feature__info-heading"},[t._v("ニコニコ実況にコメントする")]),e("span",{staticClass:"account-feature__info-text"},[t._v("テレビを見ながらニコニコ実況にコメントできます。別途、ニコニコアカウントとの連携が必要です。")])])],1),e("div",{staticClass:"account-feature"},[e("Icon",{staticClass:"account-feature__icon",attrs:{icon:"fa-brands:twitter"}}),e("div",{staticClass:"account-feature__info"},[e("span",{staticClass:"account-feature__info-heading"},[t._v("Twitter 連携機能")]),e("span",{staticClass:"account-feature__info-text"},[t._v("テレビを見ながら Twitter にツイートしたり、検索したツイートをリアルタイムで表示できます。別途、Twitter アカウントとの連携が必要です。")])])],1),e("div",{staticClass:"account-feature"},[e("Icon",{staticClass:"account-feature__icon",attrs:{icon:"fluent:arrow-sync-20-filled"}}),e("div",{staticClass:"account-feature__info"},[e("span",{staticClass:"account-feature__info-heading"},[t._v("設定をデバイス間で同期")]),e("span",{staticClass:"account-feature__info-text"},[t._v("ピン留めしたチャンネルなど、ブラウザに保存されている各種設定をブラウザやデバイスをまたいで同期できます。")])])],1),e("div",{staticClass:"account-feature"},[e("Icon",{staticClass:"account-feature__icon",attrs:{icon:"fa-solid:sliders-h"}}),e("div",{staticClass:"account-feature__info"},[e("span",{staticClass:"account-feature__info-heading"},[t._v("環境設定をブラウザから変更")]),e("span",{staticClass:"account-feature__info-text"},[t._v("管理者権限があれば、環境設定をブラウザから変更できます。一番最初に作成されたアカウントには、自動で管理者権限が付与されます。")])])],1)]),e("div",{staticClass:"account-register__description"},[t._v(" KonomiTV アカウントの作成に必要なものは"),e("br",{staticClass:"smartphone-vertical-only"}),t._v("ユーザー名とパスワードだけです。"),e("br"),t._v(" アカウントはローカルにインストールした"),e("br",{staticClass:"smartphone-vertical-only"}),t._v(" KonomiTV サーバーごとに保存されます。"),e("br"),t._v(" 外部のサービスには保存されませんので、ご安心ください。"),e("br")]),e(j.Z,{staticClass:"account-register__button",attrs:{color:"secondary",width:"100%","max-width":"250",height:"50",depressed:"",to:"/register/"}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:person-add-20-filled",height:"24"}}),t._v("アカウントを作成 ")],1)],1):t._e(),!0===t.userStore.is_logged_in?e("div",[e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"sync_settings"}},[t._v("設定をデバイス間で同期する")]),e("label",{staticClass:"settings__item-label",attrs:{for:"sync_settings"}},[t._v(" KonomiTV では、設定を同じアカウントでログインしているデバイス間で同期できます!"),e("br"),t._v(" 同期をオンにすると、同期をオンにしているすべてのデバイスで共通の設定が使えます。ピン留めチャンネルやハッシュタグリストなども同期されます。"),e("br"),t._v(" なお、デバイス固有の設定(画質設定など)は、同期後も各デバイスで個別に反映されます。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"sync_settings",inset:"","hide-details":""},model:{value:t.sync_settings,callback:function(e){t.sync_settings=e},expression:"sync_settings"}})],1),e(le.Z,{attrs:{"max-width":"530"},model:{value:t.sync_settings_dialog,callback:function(e){t.sync_settings_dialog=e},expression:"sync_settings_dialog"}},[e(pt.Z,[e(gt.EB,{staticClass:"justify-center"},[t._v("設定データの競合")]),e(gt.ZB,[t._v(" このデバイスの設定と、サーバーに保存されている設定が競合しています。"),e("br"),t._v(" 一度上書きすると、元に戻すことはできません。慎重に選択してください。"),e("br")]),e("div",{staticClass:"d-flex flex-column px-4 pb-4 settings__conflict-dialog"},[e(j.Z,{staticClass:"settings__save-button error--text text--lighten-1",attrs:{depressed:""},on:{click:function(e){return t.overrideServerSettingsFromClient()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:document-arrow-up-16-filled",height:"22px"}}),t._v(" サーバーに保存されている設定を、"),e("br",{staticClass:"smartphone-vertical-only"}),t._v("このデバイスの設定で上書きする ")],1),e(j.Z,{staticClass:"settings__save-button error--text text--lighten-1 mt-3",attrs:{depressed:""},on:{click:function(e){return t.overrideClientSettingsFromServer()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:document-arrow-down-16-filled",height:"22px"}}),t._v(" このデバイスの設定を、"),e("br",{staticClass:"smartphone-vertical-only"}),t._v("サーバーに保存されている設定で上書きする ")],1),e(j.Z,{staticClass:"settings__save-button mt-3",attrs:{depressed:""},on:{click:function(e){t.sync_settings_dialog=!1}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:dismiss-16-filled",height:"22px"}}),t._v(" キャンセル ")],1)],1)],1)],1),e(ft.Z,{ref:"settings_username",staticClass:"settings__item",on:{submit:function(t){t.preventDefault()}}},[e("div",{staticClass:"settings__item-heading"},[t._v("ユーザー名")]),e("div",{staticClass:"settings__item-label"},[t._v(" KonomiTV アカウントのユーザー名を設定します。アルファベットだけでなく日本語や記号も使えます。"),e("br"),t._v(" 同じ KonomiTV サーバー上の他のアカウントと同じユーザー名には変更できません。"),e("br")]),e(yt.Z,{staticClass:"settings__item-form",attrs:{outlined:"",placeholder:"ユーザー名",dense:t.is_form_dense,rules:[t.settings_username_validation]},model:{value:t.settings_username,callback:function(e){t.settings_username=e},expression:"settings_username"}})],1),e(j.Z,{staticClass:"settings__save-button",attrs:{depressed:""},on:{click:function(e){return t.updateAccountInfo("username")}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:save-16-filled",height:"24px"}}),t._v("ユーザー名を更新 ")],1),e(ft.Z,{staticClass:"settings__item",on:{submit:function(t){t.preventDefault()}}},[e("div",{staticClass:"settings__item-heading"},[t._v("アイコン画像")]),e("div",{staticClass:"settings__item-label"},[t._v(" KonomiTV アカウントのアイコン画像を設定します。"),e("br"),t._v(" アップロードされた画像は自動で 400×400 の正方形にリサイズされます。"),e("br")]),e(ce.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",placeholder:"アイコン画像を選択",dense:t.is_form_dense,accept:"image/jpeg, image/png","prepend-icon":"","prepend-inner-icon":"mdi-paperclip"},model:{value:t.settings_icon,callback:function(e){t.settings_icon=e},expression:"settings_icon"}})],1),e(j.Z,{staticClass:"settings__save-button mt-5",attrs:{depressed:""},on:{click:function(e){return t.updateAccountIcon()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:save-16-filled",height:"24px"}}),t._v("アイコン画像を更新 ")],1),e(ft.Z,{ref:"settings_password",staticClass:"settings__item",on:{submit:function(t){t.preventDefault()}}},[e("div",{staticClass:"settings__item-heading"},[t._v("新しいパスワード")]),e("div",{staticClass:"settings__item-label"},[t._v(" KonomiTV アカウントの新しいパスワードを設定します。"),e("br")]),e(yt.Z,{staticClass:"settings__item-form",attrs:{outlined:"",placeholder:"新しいパスワード",dense:t.is_form_dense,type:t.settings_password_showing?"text":"password","append-icon":t.settings_password_showing?"mdi-eye":"mdi-eye-off",rules:[t.settings_password_validation]},on:{"click:append":function(e){t.settings_password_showing=!t.settings_password_showing}},model:{value:t.settings_password,callback:function(e){t.settings_password=e},expression:"settings_password"}})],1),e(j.Z,{staticClass:"settings__save-button",attrs:{depressed:""},on:{click:function(e){return t.updateAccountInfo("password")}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:save-16-filled",height:"24px"}}),t._v("パスワードを更新 ")],1),e(vt.Z,{staticClass:"mt-6"}),e("div",{staticClass:"settings__item mt-6"},[e("div",{staticClass:"settings__item-heading error--text text--lighten-1"},[t._v("アカウントを削除")]),e("div",{staticClass:"settings__item-label"},[t._v(" 現在ログインしている KonomiTV アカウントを削除します。"),e("br"),e("b",[t._v("アカウントに紐づくすべてのデータが削除されます。")]),t._v("元に戻すことはできません。"),e("br")])]),e(le.Z,{attrs:{"max-width":"385"},scopedSlots:t._u([{key:"activator",fn:function({on:s}){return[e(j.Z,t._g({staticClass:"settings__save-button error mt-5",attrs:{depressed:""}},s),[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:delete-16-filled",height:"24px"}}),t._v("アカウントを削除 ")],1)]}}],null,!1,1849668703),model:{value:t.account_delete_confirm_dialog,callback:function(e){t.account_delete_confirm_dialog=e},expression:"account_delete_confirm_dialog"}},[e(pt.Z,[e(gt.EB,{staticClass:"justify-center pt-6 font-weight-bold"},[t._v("本当にアカウントを削除しますか?")]),e(gt.ZB,{staticClass:"pt-2 pb-0"},[t._v(" アカウントに紐づくすべてのデータが削除されます。元に戻すことはできません。"),e("br"),t._v(" 本当にアカウントを削除しますか? ")]),e(gt.h7,{staticClass:"pt-4 px-6 pb-5"},[e(kt.Z),e(j.Z,{attrs:{color:"text",text:""},on:{click:function(e){t.account_delete_confirm_dialog=!1}}},[t._v("キャンセル")]),e(j.Z,{attrs:{color:"error"},on:{click:function(e){return t.deleteAccount()}}},[t._v("削除")])],1)],1)],1)],1):t._e()])])},ue=[],me=s(248),he=s(1908),pe=s(1769),ge=s(8228),ve=s(1969),fe=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"route-container"},[e("Header"),e("main",[e("Navigation"),e(pt.Z,{staticClass:"settings-container d-flex px-5 py-5 mx-auto background",attrs:{elevation:"0",width:"100%","max-width":"1000"}},[e("div",[e(ve.Z,{staticClass:"settings-navigation flex-shrink-0 background",attrs:{permanent:"",width:"195",height:"auto"}},[e(he.Z,{staticClass:"px-4"},[e(pe.km,[e("h1",[t._v("設定")])])],1),e(me.Z,{staticClass:"mt-2 px-0",attrs:{nav:""}},[e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/general"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{staticStyle:{padding:"0 3px"},attrs:{icon:"fa-solid:sliders-h",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("全般")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/caption"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{attrs:{icon:"fluent:subtitles-16-filled",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("字幕")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/capture"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{attrs:{icon:"fluent:image-multiple-16-filled",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("キャプチャ")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/account"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{attrs:{icon:"fluent:person-20-filled",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("アカウント")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/jikkyo"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{staticStyle:{padding:"0 2px"},attrs:{icon:"bi:chat-left-text-fill",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("ニコニコ実況")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/twitter"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{staticStyle:{padding:"0 1px"},attrs:{icon:"fa-brands:twitter",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("Twitter")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/server"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{attrs:{icon:"fluent:server-surface-16-filled",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("サーバー設定")])],1)],1)],1)],1)],1),e(pt.Z,{staticClass:"settings ml-5 px-7 py-7 lighten-1",attrs:{width:"100%"}},[t._t("default")],2)],1)],1)],1)},we=[],ye=o["default"].extend({name:"Settings-Base",components:{Header:It,Navigation:Rt}}),be=ye,Ce=(0,h.Z)(be,fe,we,!1,null,"d0f5a998",null),ke=Ce.exports,xe=o["default"].extend({name:"Settings-Account",components:{SettingsBase:ke},data(){return{is_form_dense:ht.isSmartphoneHorizontal(),is_loading:!0,settings_username:null,settings_username_validation:t=>""===t||null===t?"ユーザー名を入力してください。":!1!==/^.{2,}$/.test(t)||"ユーザー名は2文字以上で入力してください。",settings_password:null,settings_password_showing:!1,settings_password_validation:t=>""===t||null===t?"パスワードを入力してください。":!1!==/^[a-zA-Z0-9!-/:-@¥[-`{-~]{4,}$/.test(t)||"パスワードは4文字以上の半角英数記号を入力してください。",settings_icon:null,account_delete_confirm_dialog:!1,sync_settings:st().settings.sync_settings,sync_settings_dialog:!1}},computed:Object.assign({},(0,a.Kc)(st,R)),async created(){await this.userStore.fetchUser(),this.is_loading=!1},watch:{async sync_settings(){if(!0===this.sync_settings&&!1===this.sync_settings_dialog){const t=this.settingsStore.getSyncableClientSettings(),e=JSON.stringify(t),s=await q.fetchClientSettings();if(null===s)return void this.$message.error("サーバーから設定データを取得できませんでした。");const i=JSON.stringify(s);e!==i?(this.sync_settings_dialog=!0,this.sync_settings=!1):this.settingsStore.settings.sync_settings=!0}else!1===this.sync_settings&&!1===this.sync_settings_dialog&&(this.settingsStore.settings.sync_settings=!1)}},methods:{async overrideServerSettingsFromClient(){await this.settingsStore.syncClientSettingsToServer(!0),this.settingsStore.settings.sync_settings=!0,this.sync_settings=!0,this.sync_settings_dialog=!1},async overrideClientSettingsFromServer(){await this.settingsStore.syncClientSettingsFromServer(!0),this.settingsStore.settings.sync_settings=!0,this.sync_settings=!0,this.sync_settings_dialog=!1},async updateAccountInfo(t){if("username"===t){if(!1===this.$refs.settings_username.validate())return}else if(!1===this.$refs.settings_password.validate())return;if("username"===t){if(null===this.settings_username)return;await this.userStore.updateUser({username:this.settings_username})}else{if(null===this.settings_password)return;await this.userStore.updateUser({password:this.settings_password})}},async updateAccountIcon(){null!==this.settings_icon?await this.userStore.updateUserIcon(this.settings_icon):this.$message.error("アップロードする画像を選択してください!")},async deleteAccount(){this.account_delete_confirm_dialog=!1,await this.userStore.deleteUser()}}}),Se=xe,Oe=(0,h.Z)(Se,de,ue,!1,null,"7749b102",null),Te=Oe.exports,je=s(5022),Ie=s(2059),Pe=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("SettingsBase",[e("h2",{staticClass:"settings__heading"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"settings__back-button",attrs:{to:"/settings/"}},[e("Icon",{attrs:{icon:"fluent:arrow-left-12-filled",width:"25px"}})],1),e("Icon",{attrs:{icon:"fluent:subtitles-16-filled",width:"25px"}}),e("span",{staticClass:"ml-3"},[t._v("字幕")])],1),e("div",{staticClass:"settings__content"},[e("div",{staticClass:"settings__item"},[e("label",{staticClass:"settings__item-heading"},[t._v("字幕のフォント")]),e("label",{staticClass:"settings__item-label"},[t._v(" プレイヤーで字幕表示をオンにしているときの、字幕のフォントを設定します。"),e("br")]),e(je.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",dense:t.is_form_dense,items:t.caption_font},model:{value:t.settingsStore.settings.caption_font,callback:function(e){t.$set(t.settingsStore.settings,"caption_font",e)},expression:"settingsStore.settings.caption_font"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"always_border_caption_text"}},[t._v("字幕の文字を常に縁取りする")]),e("label",{staticClass:"settings__item-label",attrs:{for:"always_border_caption_text"}},[t._v(" 字幕表示時、縁取りをオンにすると、字幕が見やすくきれいになります。とくに理由がなければ、オンにしておくのがおすすめです。"),e("br"),t._v(" この設定がオフのときも、字幕データ側で縁取りが指定されていれば、オンのときと同様に縁取り付きで描画されます。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"always_border_caption_text",inset:"","hide-details":""},model:{value:t.settingsStore.settings.always_border_caption_text,callback:function(e){t.$set(t.settingsStore.settings,"always_border_caption_text",e)},expression:"settingsStore.settings.always_border_caption_text"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"specify_caption_opacity"}},[t._v("字幕の不透明度を指定する")]),e("label",{staticClass:"settings__item-label",attrs:{for:"specify_caption_opacity"}},[t._v(" 字幕表示時、不透明度を自分で指定するか設定できます。"),e("br"),t._v(" この設定がオフのときは、字幕データ側で指定されている不透明度で描画します。とくに理由がなければ、オフにしておくのがおすすめです。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"specify_caption_opacity",inset:"","hide-details":""},model:{value:t.settingsStore.settings.specify_caption_opacity,callback:function(e){t.$set(t.settingsStore.settings,"specify_caption_opacity",e)},expression:"settingsStore.settings.specify_caption_opacity"}})],1),e("div",{staticClass:"settings__item",class:{"settings__item--disabled":!1===t.settingsStore.settings.specify_caption_opacity}},[e("label",{staticClass:"settings__item-heading"},[t._v("字幕の不透明度")]),e("label",{staticClass:"settings__item-label"},[t._v(" 上の [字幕の不透明度を指定する] をオンに設定したときのみ有効です。不透明度を 0 に設定すれば、字幕の背景を非表示にできます。"),e("br")]),e("div",{ref:"caption_opacity",staticClass:"settings__item-label"},[e(Ie.Z,{staticClass:"settings__item-form",attrs:{ticks:"always","thumb-label":"","hide-details":"",min:0,max:1,step:.05,disabled:!1===t.settingsStore.settings.specify_caption_opacity},model:{value:t.settingsStore.settings.caption_opacity,callback:function(e){t.$set(t.settingsStore.settings,"caption_opacity",e)},expression:"settingsStore.settings.caption_opacity"}})],1)]),e(vt.Z,{staticClass:"mt-6"}),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"tv_show_superimpose"}},[t._v("テレビをみるときに文字スーパーを表示する")]),e("label",{staticClass:"settings__item-label",attrs:{for:"tv_show_superimpose"}},[t._v(" 文字スーパーは、緊急地震速報の赤テロップや、NHK BS のニュース速報のテロップなどで利用されています。とくに理由がなければ、オンにしておくのがおすすめです。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"tv_show_superimpose",inset:"","hide-details":""},model:{value:t.settingsStore.settings.tv_show_superimpose,callback:function(e){t.$set(t.settingsStore.settings,"tv_show_superimpose",e)},expression:"settingsStore.settings.tv_show_superimpose"}})],1)],1)])},Ze=[],ze=o["default"].extend({name:"Settings-Caption",components:{SettingsBase:ke},data(){return{is_form_dense:ht.isSmartphoneHorizontal(),caption_font:[{text:"Windows TV ゴシック",value:"Windows TV Gothic"},{text:"Windows TV 丸ゴシック",value:"Windows TV MaruGothic"},{text:"Windows TV 太丸ゴシック",value:"Windows TV FutoMaruGothic"},{text:"ヒラギノTV丸ゴ",value:"Hiragino TV Sans Rd S"},{text:"新丸ゴ ARIB",value:"TT-ShinMGo-regular"},{text:"Rounded M+ 1m for ARIB",value:"Rounded M+ 1m for ARIB"},{text:"Noto Sans JP",value:"Noto Sans JP Caption"},{text:"デフォルトのフォント",value:"sans-serif"}]}},computed:Object.assign({},(0,a.Kc)(st))}),$e=ze,Ae=(0,h.Z)($e,Pe,Ze,!1,null,null,null),De=Ae.exports,Be=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("SettingsBase",[e("h2",{staticClass:"settings__heading"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"settings__back-button",attrs:{to:"/settings/"}},[e("Icon",{attrs:{icon:"fluent:arrow-left-12-filled",width:"25px"}})],1),e("Icon",{attrs:{icon:"fluent:image-multiple-16-filled",width:"26px"}}),e("span",{staticClass:"ml-2"},[t._v("キャプチャ")])],1),e("div",{staticClass:"settings__content"},[e("div",{staticClass:"settings__item settings__item--switch settings__item--sync-disabled"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"capture_copy_to_clipboard"}},[t._v("キャプチャをクリップボードにコピーする")]),e("label",{staticClass:"settings__item-label",attrs:{for:"capture_copy_to_clipboard"}},[t._v(" この設定をオンにすると、撮ったキャプチャ画像がクリップボードにもコピーされます。"),e("br"),t._v(" クリップボードの履歴をサポートしていない OS では、この設定をオンにしてキャプチャを撮ると、以前のクリップボードが上書きされます。注意してください。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"capture_copy_to_clipboard",inset:"","hide-details":""},model:{value:t.settingsStore.settings.capture_copy_to_clipboard,callback:function(e){t.$set(t.settingsStore.settings,"capture_copy_to_clipboard",e)},expression:"settingsStore.settings.capture_copy_to_clipboard"}})],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("キャプチャの保存先")]),e("div",{staticClass:"settings__item-label"},[e("p",[t._v(" キャプチャした画像をブラウザでダウンロードするか、KonomiTV サーバーにアップロードするかを設定します。"),e("br"),t._v(" ブラウザでのダウンロードと、KonomiTV サーバーへのアップロードを両方同時に行うこともできます。"),e("br")]),e("p",[t._v(" ブラウザでダウンロードすると、視聴中のデバイスのダウンロードフォルダに保存されます。"),e("br"),t._v(" 視聴中のデバイスにそのまま保存されるためシンプルですが、保存先のフォルダを変更できないこと、PC 版 Chrome では毎回ダウンロードバーが表示されてしまったり、iOS Safari (PWA モード) ではダウンロードするとファイル概要画面が表示され再生が中断してしまったりなど、視聴に支障することがデメリットです (将来的には、iOS / Android アプリ版や拡張機能などで解消される予定) 。"),e("br")]),e("p",[t._v(" KonomiTV サーバーにアップロードすると、環境設定で指定されたキャプチャ保存フォルダに保存されます。視聴したデバイスにかかわらず、今までに撮ったキャプチャをひとつのフォルダにまとめて保存できます。"),e("br"),t._v(" 他のデバイスでキャプチャを見るにはキャプチャ保存フォルダをネットワークに共有する必要があること、スマホ・タブレットではネットワーク上のフォルダへのアクセスがやや面倒なことがデメリットです。(将来的には、保存フォルダ内のキャプチャを Google フォトのように表示する機能を追加予定)"),e("br")])]),e(je.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",dense:t.is_form_dense,items:t.capture_save_mode},model:{value:t.settingsStore.settings.capture_save_mode,callback:function(e){t.$set(t.settingsStore.settings,"capture_save_mode",e)},expression:"settingsStore.settings.capture_save_mode"}})],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("字幕表示時のキャプチャの保存モード")]),e("div",{staticClass:"settings__item-label"},[t._v(" 字幕表示時、キャプチャした画像に字幕を合成するかを設定します。"),e("br"),t._v(" 映像のみのキャプチャと、字幕を合成したキャプチャを両方同時に保存することもできます。"),e("br"),t._v(" なお、字幕非表示時は、常に映像のみ (+コメント付きキャプチャではコメントを合成して) 保存されます。"),e("br")]),e(je.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",dense:t.is_form_dense,items:t.capture_caption_mode},model:{value:t.settingsStore.settings.capture_caption_mode,callback:function(e){t.$set(t.settingsStore.settings,"capture_caption_mode",e)},expression:"settingsStore.settings.capture_caption_mode"}})],1)])])},Ne=[],Ke=o["default"].extend({name:"Settings-Capture",components:{SettingsBase:ke},data(){return{is_form_dense:ht.isSmartphoneHorizontal(),capture_save_mode:[{text:"ブラウザでダウンロード",value:"Browser"},{text:"KonomiTV サーバーにアップロード",value:"UploadServer"},{text:"ブラウザでのダウンロードと、KonomiTV サーバーへのアップロードを両方行う",value:"Both"}],capture_caption_mode:[{text:"映像のみのキャプチャを保存する",value:"VideoOnly"},{text:"字幕を合成したキャプチャを保存する",value:"CompositingCaption"},{text:"映像のみのキャプチャと、字幕を合成したキャプチャを両方保存する",value:"Both"}]}},computed:Object.assign({},(0,a.Kc)(st))}),Le=Ke,Ee=(0,h.Z)(Le,Be,Ne,!1,null,null,null),He=Ee.exports,Me=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("SettingsBase",[e("h2",{staticClass:"settings__heading"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"settings__back-button",attrs:{to:"/settings/"}},[e("Icon",{attrs:{icon:"fluent:arrow-left-12-filled",width:"25px"}})],1),e("Icon",{attrs:{icon:"fa-solid:sliders-h",width:"19px"}}),e("span",{staticClass:"ml-3"},[t._v("全般")])],1),e("div",{staticClass:"settings__content"},[e("div",{staticClass:"settings__item settings__item--sync-disabled"},[e("div",{staticClass:"settings__item-heading"},[t._v("テレビのデフォルトのストリーミング画質")]),e("div",{staticClass:"settings__item-label"},[t._v(" テレビをライブストリーミングするときのデフォルトの画質を設定します。"),e("br"),t._v(" ストリーミング画質はプレイヤーの設定からいつでも切り替えられます。"),e("br")]),e("div",{staticClass:"settings__item-label"},[t._v(" [1080p (60fps)] は、通常 30fps (60i) の映像を補間し、より滑らか(ぬるぬる)な映像で視聴できます!"),e("br"),t._v(" [1080p (60fps)] で視聴するときは、環境設定の [利用するエンコーダー] をハードウェアエンコーダーに設定してください。FFmpeg (ソフトウェアエンコーダー) では、再生に支障が出ることがあります。"),e("br")]),e(je.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",dense:t.is_form_dense,items:t.tv_streaming_quality},model:{value:t.settingsStore.settings.tv_streaming_quality,callback:function(e){t.$set(t.settingsStore.settings,"tv_streaming_quality",e)},expression:"settingsStore.settings.tv_streaming_quality"}})],1),e("div",{staticClass:"settings__item settings__item--switch settings__item--sync-disabled",class:{"settings__item--disabled":!1===t.PlayerUtils.isHEVCVideoSupported()}},[e("label",{staticClass:"settings__item-heading",attrs:{for:"tv_data_saver_mode"}},[t._v("テレビを通信節約モードで視聴する")]),e("label",{staticClass:"settings__item-label",attrs:{for:"tv_data_saver_mode"}},[t._v(" 通信節約モードでは、H.265 / HEVC という圧縮率の高いコーデックを使い、画質はほぼそのまま、通信量を通常の 1/2 程度に抑えながら視聴できます!"),e("br"),t._v(" 通信節約モードで視聴するときは、環境設定の [利用するエンコーダー] をハードウェアエンコーダーに設定してください。FFmpeg (ソフトウェアエンコーダー) では、再生に支障が出る可能性が高いです。"),e("br"),!1===t.PlayerUtils.isHEVCVideoSupported()&&!1===t.Utils.isFirefox()?e("p",{staticClass:"mt-1 mb-0 error--text lighten-1"},[t._v(" このデバイスでは通信節約モードがサポートされていません。 ")]):t._e(),!1===t.PlayerUtils.isHEVCVideoSupported()&&!0===t.Utils.isFirefox()?e("p",{staticClass:"mt-1 mb-0 error--text lighten-1"},[t._v(" お使いの Firefox ブラウザでは通信節約モードがサポートされていません。 ")]):t._e()]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"tv_data_saver_mode",inset:"","hide-details":"",disabled:!1===t.PlayerUtils.isHEVCVideoSupported()},model:{value:t.settingsStore.settings.tv_data_saver_mode,callback:function(e){t.$set(t.settingsStore.settings,"tv_data_saver_mode",e)},expression:"settingsStore.settings.tv_data_saver_mode"}})],1),e("div",{staticClass:"settings__item settings__item--switch settings__item--sync-disabled"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"tv_low_latency_mode"}},[t._v("テレビを低遅延で視聴する")]),e("label",{staticClass:"settings__item-label",attrs:{for:"tv_low_latency_mode"}},[t._v(" 低遅延ストリーミングをオンにすると、"),e("b",[t._v("放送波との遅延を最短 0.9 秒に抑えて視聴できます!")]),e("br"),t._v(" また、約 3 秒以上遅延したときに少しだけ再生速度を早める (1.1x) ことで、滑らかにストリーミングの遅延を取り戻します。"),e("br"),t._v(" 宅外視聴などのネットワークが不安定になりがちな環境では、低遅延ストリーミングをオフにしてみると、映像のカクつきを改善できるかもしれません。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"tv_low_latency_mode",inset:"","hide-details":""},model:{value:t.settingsStore.settings.tv_low_latency_mode,callback:function(e){t.$set(t.settingsStore.settings,"tv_low_latency_mode",e)},expression:"settingsStore.settings.tv_low_latency_mode"}})],1),e(vt.Z,{staticClass:"mt-6"}),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("デフォルトのパネルの表示状態")]),e("div",{staticClass:"settings__item-label"},[t._v(" 視聴画面を開いたときに、右側のパネルをどう表示するかを設定します。"),e("br")]),e(je.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",dense:t.is_form_dense,items:t.panel_display_state},model:{value:t.settingsStore.settings.panel_display_state,callback:function(e){t.$set(t.settingsStore.settings,"panel_display_state",e)},expression:"settingsStore.settings.panel_display_state"}})],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("テレビをみるときにデフォルトで表示されるパネルのタブ")]),e("div",{staticClass:"settings__item-label"},[t._v(" テレビの視聴画面を開いたときに、右側のパネルで最初に表示されるタブを設定します。"),e("br")]),e(je.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",dense:t.is_form_dense,items:t.tv_panel_active_tab},model:{value:t.settingsStore.settings.tv_panel_active_tab,callback:function(e){t.$set(t.settingsStore.settings,"tv_panel_active_tab",e)},expression:"settingsStore.settings.tv_panel_active_tab"}})],1),e(vt.Z,{staticClass:"mt-6"}),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"tv_show_superimpose"}},[t._v("チャンネル選局のキーボードショートカットを "+t._s(t.Utils.AltOrOption())+" + 数字キー/テンキーに変更する")]),e("label",{staticClass:"settings__item-label",attrs:{for:"tv_show_superimpose"}},[t._v(" この設定をオンにすると、数字キーまたはテンキーに対応するリモコン番号(1~12)のチャンネルに切り替える際、"+t._s(t.Utils.AltOrOption())+" キーを同時に押す必要があります。"),e("br"),t._v(" コメントやツイートを入力しようとして誤って数字キーを押してしまい、チャンネルが変わってしまう事態を避けたい方におすすめです。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"tv_show_superimpose",inset:"","hide-details":""},model:{value:t.settingsStore.settings.tv_channel_selection_requires_alt_key,callback:function(e){t.$set(t.settingsStore.settings,"tv_channel_selection_requires_alt_key",e)},expression:"settingsStore.settings.tv_channel_selection_requires_alt_key"}})],1),e(vt.Z,{staticClass:"mt-6"}),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("設定をエクスポート")]),e("div",{staticClass:"settings__item-label"},[t._v(" このデバイス(ブラウザ)に保存されている設定データを、エクスポート(ダウンロード)できます。"),e("br"),t._v(" ダウンロードした設定データ (KonomiTV-Settings.json) は、[設定をインポート] からインポートできます。異なるサーバーの KonomiTV を同じ設定で使いたいときなどに使ってください。"),e("br")])]),e(j.Z,{staticClass:"settings__save-button mt-4",attrs:{depressed:""},on:{click:function(e){return t.exportSettings()}}},[e("Icon",{staticClass:"mr-3",attrs:{icon:"fa6-solid:download",height:"19px"}}),t._v("設定をエクスポート ")],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading error--text text--lighten-1"},[t._v("設定をインポート")]),e("div",{staticClass:"settings__item-label"},[t._v(" [設定をエクスポート] でダウンロードした設定データを、このデバイス(ブラウザ)にインポートできます。"),e("br"),t._v(" 設定をインポートすると、"),e("b",[t._v("現在のデバイス設定はすべて上書きされます。")]),t._v("元に戻すことはできません。"),e("br"),t._v(" 設定のデバイス間同期がオンのときは、"),e("b",[t._v("同期が有効なすべてのデバイスに反映されます。")]),t._v("十分ご注意ください。"),e("br")]),e(ce.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",placeholder:"設定データ (KonomiTV-Settings.json) を選択",dense:t.is_form_dense,accept:"application/json","prepend-icon":"","prepend-inner-icon":"mdi-paperclip"},model:{value:t.import_settings_file,callback:function(e){t.import_settings_file=e},expression:"import_settings_file"}})],1),e(j.Z,{staticClass:"settings__save-button error mt-5",attrs:{depressed:""},on:{click:function(e){return t.importSettings()}}},[e("Icon",{staticClass:"mr-3",attrs:{icon:"fa6-solid:upload",height:"19px"}}),t._v("設定をインポート ")],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading error--text text--lighten-1"},[t._v("設定を初期状態にリセット")]),e("div",{staticClass:"settings__item-label"},[t._v(" このデバイス(ブラウザ)に保存されている設定データを、初期状態のデフォルト値にリセットできます。"),e("br"),t._v(" 設定をリセットすると、元に戻すことはできません。"),e("br"),t._v(" 設定のデバイス間同期がオンのときは、"),e("b",[t._v("同期が有効なすべてのデバイスに反映されます。")]),t._v("十分ご注意ください。"),e("br")])]),e(j.Z,{staticClass:"settings__save-button error mt-5",attrs:{depressed:""},on:{click:function(e){return t.resetSettings()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"material-symbols:device-reset-rounded",height:"23px"}}),t._v("設定をリセット ")],1)],1)])},Ue=[];const Ve=[{text:"1080p (60fps) (約4.50GB/h / 平均10.0Mbps)",value:"1080p-60fps"},{text:"1080p (約4.50GB/h / 平均10.0Mbps)",value:"1080p"},{text:"810p (約2.62GB/h / 平均5.8Mbps)",value:"810p"},{text:"720p (約2.18GB/h / 平均4.9Mbps)",value:"720p"},{text:"540p (約1.52GB/h / 平均3.4Mbps)",value:"540p"},{text:"480p (約1.06GB/h / 平均2.3Mbps)",value:"480p"},{text:"360p (約0.60GB/h / 平均1.3Mbps)",value:"360p"},{text:"240p (約0.35GB/h / 平均0.8Mbps)",value:"240p"}],Re=[{text:"1080p (60fps) (約1.80GB/h / 平均4.0Mbps)",value:"1080p-60fps"},{text:"1080p (約1.37GB/h / 平均3.0Mbps)",value:"1080p"},{text:"810p (約1.05GB/h / 平均2.3Mbps)",value:"810p"},{text:"720p (約0.82GB/h / 平均1.8Mbps)",value:"720p"},{text:"540p (約0.53GB/h / 平均1.2Mbps)",value:"540p"},{text:"480p (約0.46GB/h / 平均1.0Mbps)",value:"480p"},{text:"360p (約0.30GB/h / 平均0.7Mbps)",value:"360p"},{text:"240p (約0.20GB/h / 平均0.4Mbps)",value:"240p"}];var Fe=o["default"].extend({name:"Settings-General",components:{SettingsBase:ke},data(){return{Utils:ht,PlayerUtils:at,is_form_dense:ht.isSmartphoneHorizontal(),tv_streaming_quality:Ve,panel_display_state:[{text:"前回の状態を復元する",value:"RestorePreviousState"},{text:"常に表示する",value:"AlwaysDisplay"},{text:"常に折りたたむ",value:"AlwaysFold"}],tv_panel_active_tab:[{text:"番組情報タブ",value:"Program"},{text:"チャンネルタブ",value:"Channel"},{text:"コメントタブ",value:"Comment"},{text:"Twitter タブ",value:"Twitter"}],import_settings_file:null}},computed:Object.assign({},(0,a.Kc)(st)),created(){!0===this.settingsStore.settings.tv_data_saver_mode&&(this.tv_streaming_quality=Re)},watch:{"settingsStore.settings.tv_data_saver_mode":{immediate:!0,handler(t){this.tv_streaming_quality=!0===t?Re:Ve}}},methods:{exportSettings(){const t=JSON.stringify(this.settingsStore.settings,null,4),e=new Blob([t],{type:"application/json"});ht.downloadBlobData(e,"KonomiTV-Settings.json"),this.$message.success("設定をエクスポートしました。")},async importSettings(){if(null===this.import_settings_file)return void this.$message.error("インポートする設定データを選択してください!");const t=await this.settingsStore.importClientSettings(this.import_settings_file);!0===t?(this.$message.success("設定をインポートしました。"),window.setTimeout((()=>this.$router.go(0)),300)):this.$message.error("設定データが不正なため、インポートできませんでした。")},async resetSettings(){await this.settingsStore.resetClientSettings(),this.$message.success("設定をリセットしました。"),window.setTimeout((()=>this.$router.go(0)),300)}}}),Ge=Fe,We=(0,h.Z)(Ge,Me,Ue,!1,null,null,null),qe=We.exports,Xe=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"route-container"},[e("Header"),e("main",[e("Navigation"),e(pt.Z,{staticClass:"settings-container d-flex px-5 py-5 mx-auto background",attrs:{elevation:"0",width:"100%","max-width":"1000"}},[e(ve.Z,{staticClass:"settings-navigation flex-shrink-0 background",attrs:{permanent:"",width:"100%",height:"auto"}},[e(he.Z,{staticClass:"px-1"},[e(pe.km,[e("h1",[t._v("設定")])])],1),e(me.Z,{staticClass:"mt-2 px-0",attrs:{nav:""}},[e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/general"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{staticStyle:{padding:"0 3px"},attrs:{icon:"fa-solid:sliders-h",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("全般")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/caption"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{attrs:{icon:"fluent:subtitles-16-filled",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("字幕")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/capture"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{attrs:{icon:"fluent:image-multiple-16-filled",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("キャプチャ")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/account"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{attrs:{icon:"fluent:person-20-filled",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("アカウント")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/jikkyo"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{staticStyle:{padding:"0 2px"},attrs:{icon:"bi:chat-left-text-fill",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("ニコニコ実況")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/twitter"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{staticStyle:{padding:"0 1px"},attrs:{icon:"fa-brands:twitter",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("Twitter")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/server"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{attrs:{icon:"fluent:server-surface-16-filled",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("サーバー設定")])],1)],1),e(he.Z,{staticClass:"px-4 settings-navigation-version",class:{"settings-navigation-version--highlight":t.versionStore.is_update_available},attrs:{link:"",color:"primary",href:"https://github.com/tsukumijima/KonomiTV"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{attrs:{icon:"fluent:info-16-regular",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v(" version "+t._s(t.versionStore.client_version)+t._s(t.versionStore.is_update_available?" (Update Available)":"")+" ")])],1)],1)],1)],1)],1)],1)],1)},Ye=[],Je=o["default"].extend({name:"Settings-Index",components:{Header:It,Navigation:Rt},computed:Object.assign({},(0,a.Kc)(Ht)),async created(){await this.versionStore.fetchServerVersion()}}),Qe=Je,ts=(0,h.Z)(Qe,Xe,Ye,!1,null,"48d089f3",null),es=ts.exports,ss=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("SettingsBase",[e("h2",{staticClass:"settings__heading"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"settings__back-button",attrs:{to:"/settings/"}},[e("Icon",{attrs:{icon:"fluent:arrow-left-12-filled",width:"25px"}})],1),e("Icon",{attrs:{icon:"bi:chat-left-text-fill",width:"19px"}}),e("span",{staticClass:"ml-3"},[t._v("ニコニコ実況")])],1),e("div",{staticClass:"settings__content",class:{"settings__content--loading":t.is_loading}},[null===t.userStore.user||null===t.userStore.user.niconico_user_id?e("div",{staticClass:"niconico-account niconico-account--anonymous"},[e("div",{staticClass:"niconico-account-wrapper"},[e("Icon",{staticClass:"flex-shrink-0",attrs:{icon:"bi:chat-left-text-fill",width:"45px"}}),e("div",{staticClass:"niconico-account__info ml-4"},[e("div",{staticClass:"niconico-account__info-name"},[e("span",{staticClass:"niconico-account__info-name-text"},[t._v("ニコニコアカウントと連携していません")])]),e("span",{staticClass:"niconico-account__info-description"},[t._v(" ニコニコアカウントと連携すると、テレビを見ながらニコニコ実況にコメントできるようになります。 ")])])],1),e(j.Z,{staticClass:"niconico-account__login ml-auto",attrs:{color:"secondary",width:"130",height:"56",depressed:""},on:{click:function(e){return t.loginNiconicoAccount()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:plug-connected-20-filled",height:"26"}}),t._v("連携する ")],1)],1):t._e(),null!==t.userStore.user&&null!==t.userStore.user.niconico_user_id?e("div",{staticClass:"niconico-account"},[e("div",{staticClass:"niconico-account-wrapper"},[e("img",{staticClass:"niconico-account__icon",attrs:{src:t.userStore.user_niconico_icon_url}}),e("div",{staticClass:"niconico-account__info"},[e("div",{staticClass:"niconico-account__info-name"},[e("span",{staticClass:"niconico-account__info-name-text"},[t._v(t._s(t.userStore.user.niconico_user_name)+" と連携しています")])]),e("span",{staticClass:"niconico-account__info-description"},[e("span",{staticClass:"mr-2",staticStyle:{"white-space":"nowrap"}},[t._v("Niconico User ID:")]),e("a",{staticClass:"mr-2",attrs:{href:`https://www.nicovideo.jp/user/${t.userStore.user.niconico_user_id}`,target:"_blank"}},[t._v(t._s(t.userStore.user.niconico_user_id))]),1==t.userStore.user.niconico_user_premium?e("span",{staticClass:"secondary--text"},[t._v("(Premium)")]):t._e()])])]),e(j.Z,{staticClass:"niconico-account__login ml-auto",attrs:{color:"secondary",width:"130",height:"56",depressed:""},on:{click:function(e){return t.logoutNiconicoAccount()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:plug-disconnected-20-filled",height:"26"}}),t._v("連携解除 ")],1)],1):t._e(),e("div",{staticClass:"settings__item mt-7"},[e("div",{staticClass:"settings__item-heading"},[t._v("コメントのミュート設定")]),e("div",{staticClass:"settings__item-label"},[t._v(" 表示したくないコメントを、映像上やコメントリストに表示しないようにミュートできます。"),e("br")])]),e(j.Z,{staticClass:"settings__save-button mt-4",attrs:{depressed:""},on:{click:function(e){t.comment_mute_settings_modal=!t.comment_mute_settings_modal}}},[e("Icon",{attrs:{icon:"heroicons-solid:filter",height:"19px"}}),e("span",{staticClass:"ml-1"},[t._v("コメントのミュート設定を開く")])],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("コメントの速さ")]),e("div",{staticClass:"settings__item-label"},[t._v(" プレイヤーに流れるコメントの速さを設定します。"),e("br"),t._v(" たとえば 1.2 に設定すると、コメントが 1.2 倍速く流れます。"),e("br")]),e(Ie.Z,{staticClass:"settings__item-form",attrs:{ticks:"always","thumb-label":"","hide-details":"",step:.1,min:.5,max:2},model:{value:t.settingsStore.settings.comment_speed_rate,callback:function(e){t.$set(t.settingsStore.settings,"comment_speed_rate",e)},expression:"settingsStore.settings.comment_speed_rate"}})],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("コメントの文字サイズ")]),e("div",{staticClass:"settings__item-label"},[t._v(" プレイヤーに流れるコメントの文字サイズの基準値を設定します。"),e("br"),t._v(" 実際の文字サイズは画面サイズに合わせて調整されます。デフォルトの文字サイズは 34px です。"),e("br")]),e(Ie.Z,{staticClass:"settings__item-form",attrs:{ticks:"always","thumb-label":"","hide-details":"",min:20,max:60},model:{value:t.settingsStore.settings.comment_font_size,callback:function(e){t.$set(t.settingsStore.settings,"comment_font_size",e)},expression:"settingsStore.settings.comment_font_size"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"close_comment_form_after_sending"}},[t._v("コメント送信後にコメント入力フォームを閉じる")]),e("label",{staticClass:"settings__item-label",attrs:{for:"close_comment_form_after_sending"}},[t._v(" この設定をオンにすると、コメントを送信した後に、コメント入力フォームが自動で閉じるようになります。"),e("br"),t._v(" コメント入力フォームが表示されたままだと、大半のショートカットキーが文字入力と競合して使えなくなります。とくに理由がなければ、オンにしておくのがおすすめです。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"close_comment_form_after_sending",inset:"","hide-details":""},model:{value:t.settingsStore.settings.close_comment_form_after_sending,callback:function(e){t.$set(t.settingsStore.settings,"close_comment_form_after_sending",e)},expression:"settingsStore.settings.close_comment_form_after_sending"}})],1)],1),e("CommentMuteSettings",{model:{value:t.comment_mute_settings_modal,callback:function(e){t.comment_mute_settings_modal=e},expression:"comment_mute_settings_modal"}})],1)},is=[],as=function(){var t=this,e=t._self._c;t._self._setupProxy;return e(le.Z,{attrs:{"max-width":"770",transition:"slide-y-transition"},model:{value:t.comment_mute_settings_modal,callback:function(e){t.comment_mute_settings_modal=e},expression:"comment_mute_settings_modal"}},[e(pt.Z,{staticClass:"comment-mute-settings"},[e(gt.EB,{staticClass:"px-5 pt-5 pb-3 d-flex align-center font-weight-bold",staticStyle:{height:"60px"}},[e("Icon",{attrs:{icon:"heroicons-solid:filter",height:"26px"}}),e("span",{staticClass:"ml-3"},[t._v("コメントのミュート設定")]),e(kt.Z),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"d-flex align-center rounded-circle cursor-pointer px-2 py-2",on:{click:function(e){t.comment_mute_settings_modal=!1}}},[e("Icon",{attrs:{icon:"fluent:dismiss-12-filled",width:"23px",height:"23px"}})],1)],1),e("div",{staticClass:"px-5 pb-5"},[e("div",{staticClass:"text-subtitle-1 d-flex align-center font-weight-bold mt-4"},[e("Icon",{attrs:{icon:"fa-solid:sliders-h",width:"24px",height:"20px"}}),e("span",{staticClass:"ml-2"},[t._v("クイック設定")])],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"mute_vulgar_comments"}},[t._v(" 露骨な表現を含むコメントをミュートする ")]),e("label",{staticClass:"settings__item-label",attrs:{for:"mute_vulgar_comments"}},[t._v(" 性的な単語などの露骨・下品な表現を含むコメントを、一括でミュートするかを設定します。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"mute_vulgar_comments",inset:"","hide-details":""},model:{value:t.settingsStore.settings.mute_vulgar_comments,callback:function(e){t.$set(t.settingsStore.settings,"mute_vulgar_comments",e)},expression:"settingsStore.settings.mute_vulgar_comments"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"mute_abusive_discriminatory_prejudiced_comments"}},[t._v(" ネガティブな表現、差別的な表現、政治的に偏った表現を含むコメントをミュートする ")]),e("label",{staticClass:"settings__item-label",attrs:{for:"mute_abusive_discriminatory_prejudiced_comments"}},[t._v(" 『死ね』『殺す』などのネガティブな表現、特定の国や人々への差別的な表現、政治的に偏った表現を含むコメントを、一括でミュートするかを設定します。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"mute_abusive_discriminatory_prejudiced_comments",inset:"","hide-details":""},model:{value:t.settingsStore.settings.mute_abusive_discriminatory_prejudiced_comments,callback:function(e){t.$set(t.settingsStore.settings,"mute_abusive_discriminatory_prejudiced_comments",e)},expression:"settingsStore.settings.mute_abusive_discriminatory_prejudiced_comments"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"mute_big_size_comments"}},[t._v(" 文字サイズが大きいコメントをミュートする ")]),e("label",{staticClass:"settings__item-label",attrs:{for:"mute_big_size_comments"}},[t._v(" 通常より大きい文字サイズで表示されるコメントを、一括でミュートするかを設定します。"),e("br"),t._v(" 文字サイズが大きいコメントには迷惑なコメントが多いです。基本的にはオンにしておくのがおすすめです。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"mute_big_size_comments",inset:"","hide-details":""},model:{value:t.settingsStore.settings.mute_big_size_comments,callback:function(e){t.$set(t.settingsStore.settings,"mute_big_size_comments",e)},expression:"settingsStore.settings.mute_big_size_comments"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"mute_fixed_comments"}},[t._v(" 映像の上下に固定表示されるコメントをミュートする ")]),e("label",{staticClass:"settings__item-label",attrs:{for:"mute_fixed_comments"}},[t._v(" 映像の上下に固定された状態で表示されるコメントを、一括でミュートするかを設定します。"),e("br"),t._v(" 固定表示されるコメントが煩わしい方におすすめです。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"mute_fixed_comments",inset:"","hide-details":""},model:{value:t.settingsStore.settings.mute_fixed_comments,callback:function(e){t.$set(t.settingsStore.settings,"mute_fixed_comments",e)},expression:"settingsStore.settings.mute_fixed_comments"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"mute_colored_comments"}},[t._v(" 色付きのコメントをミュートする ")]),e("label",{staticClass:"settings__item-label",attrs:{for:"mute_colored_comments"}},[t._v(" 白以外の色で表示される色付きのコメントを、一括でミュートするかを設定します。"),e("br"),t._v(" この設定をオンにしておくと、目立つ色のコメントを一掃できます。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"mute_colored_comments",inset:"","hide-details":""},model:{value:t.settingsStore.settings.mute_colored_comments,callback:function(e){t.$set(t.settingsStore.settings,"mute_colored_comments",e)},expression:"settingsStore.settings.mute_colored_comments"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"mute_consecutive_same_characters_comments"}},[t._v(" 8文字以上同じ文字が連続しているコメントをミュートする ")]),e("label",{staticClass:"settings__item-label",attrs:{for:"mute_consecutive_same_characters_comments"}},[t._v(" 『wwwwwwwwwww』『あばばばばばばばばば』など、8文字以上同じ文字が連続しているコメントを、一括でミュートするかを設定します。"),e("br"),t._v(" しばしばあるテンプレコメントが煩わしい方におすすめです。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"mute_consecutive_same_characters_comments",inset:"","hide-details":""},model:{value:t.settingsStore.settings.mute_consecutive_same_characters_comments,callback:function(e){t.$set(t.settingsStore.settings,"mute_consecutive_same_characters_comments",e)},expression:"settingsStore.settings.mute_consecutive_same_characters_comments"}})],1),e("div",{staticClass:"text-subtitle-1 d-flex align-center font-weight-bold mt-4"},[e("Icon",{attrs:{icon:"fluent:comment-dismiss-20-filled",width:"24px"}}),e("span",{staticClass:"ml-2 mr-2"},[t._v("ミュート済みのキーワード")]),e(j.Z,{staticClass:"ml-auto",attrs:{depressed:""},on:{click:function(e){return t.settingsStore.settings.muted_comment_keywords.push({match:"partial",pattern:""})}}},[e("Icon",{attrs:{icon:"fluent:add-12-filled",height:"17px"}}),e("span",{staticClass:"ml-1"},[t._v("追加")])],1)],1),e("div",{staticClass:"muted-comment-items"},t._l(t.settingsStore.settings.muted_comment_keywords,(function(s,i){return e("div",{key:i,staticClass:"muted-comment-item"},[e(yt.Z,{staticClass:"muted-comment-item__input",attrs:{type:"search",dense:"",outlined:"","hide-details":"",placeholder:"ミュートするキーワードを入力"},model:{value:t.settingsStore.settings.muted_comment_keywords[i].pattern,callback:function(e){t.$set(t.settingsStore.settings.muted_comment_keywords[i],"pattern",e)},expression:"settingsStore.settings.muted_comment_keywords[index].pattern"}}),e(je.Z,{staticClass:"muted-comment-item__match-type",attrs:{dense:"",outlined:"","hide-details":"",items:t.muted_comment_keyword_match_type},model:{value:t.settingsStore.settings.muted_comment_keywords[i].match,callback:function(e){t.$set(t.settingsStore.settings.muted_comment_keywords[i],"match",e)},expression:"settingsStore.settings.muted_comment_keywords[index].match"}}),e("button",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"muted-comment-item__delete-button",on:{click:function(e){t.settingsStore.settings.muted_comment_keywords.splice(t.settingsStore.settings.muted_comment_keywords.indexOf(s),1)}}},[e("Icon",{attrs:{icon:"fluent:delete-16-filled",width:"20px"}})],1)],1)})),0),e("div",{staticClass:"text-subtitle-1 d-flex align-center font-weight-bold mt-4"},[e("Icon",{attrs:{icon:"fluent:person-prohibited-20-filled",width:"24px"}}),e("span",{staticClass:"ml-2 mr-2"},[t._v("ミュート済みのニコニコユーザー ID")]),e(j.Z,{staticClass:"ml-auto",attrs:{depressed:""},on:{click:function(e){return t.settingsStore.settings.muted_niconico_user_ids.push("")}}},[e("Icon",{attrs:{icon:"fluent:add-12-filled",height:"17px"}}),e("span",{staticClass:"ml-1"},[t._v("追加")])],1)],1),e("div",{staticClass:"muted-comment-items"},t._l(t.settingsStore.settings.muted_niconico_user_ids,(function(s,i){return e("div",{key:i,staticClass:"muted-comment-item"},[e(yt.Z,{staticClass:"muted-comment-item__input",attrs:{type:"search",dense:"",outlined:"","hide-details":"",placeholder:"ミュートするニコニコユーザー ID を入力"},model:{value:t.settingsStore.settings.muted_niconico_user_ids[i],callback:function(e){t.$set(t.settingsStore.settings.muted_niconico_user_ids,i,e)},expression:"settingsStore.settings.muted_niconico_user_ids[index]"}}),e("button",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"muted-comment-item__delete-button",on:{click:function(e){t.settingsStore.settings.muted_niconico_user_ids.splice(t.settingsStore.settings.muted_niconico_user_ids.indexOf(s),1)}}},[e("Icon",{attrs:{icon:"fluent:delete-16-filled",width:"20px"}})],1)],1)})),0)])],1)],1)},ns=[],rs=o["default"].extend({name:"CommentMuteSettings",model:{prop:"showing",event:"change"},props:{showing:{type:Boolean,required:!0}},data(){return{interval_timer_id:0,comment_mute_settings_modal:!1,muted_comment_keyword_match_type:[{text:"部分一致",value:"partial"},{text:"前方一致",value:"forward"},{text:"後方一致",value:"backward"},{text:"完全一致",value:"exact"},{text:"正規表現",value:"regex"}]}},computed:Object.assign({},(0,a.Kc)(st)),watch:{showing(){this.comment_mute_settings_modal=this.showing},comment_mute_settings_modal(){this.$emit("change",this.comment_mute_settings_modal)}}}),os=rs,ls=(0,h.Z)(os,as,ns,!1,null,"2cd59ba0",null),cs=ls.exports;class _s{static async fetchAuthorizationURL(){const t=await G.get("/niconico/auth");return"is_error"in t?(G.showGenericError(t,"ニコニコアカウントとの連携用の認証 URL を取得できませんでした。"),null):t.data.authorization_url}static async logoutAccount(){const t=await G["delete"]("/niconico/logout");return!("is_error"in t)||(G.showGenericError(t,"ニコニコアカウントとの連携を解除できませんでした。"),!1)}}var ds=_s,us=o["default"].extend({name:"Settings-Jikkyo",components:{SettingsBase:ke,CommentMuteSettings:cs},data(){return{comment_mute_settings_modal:!1,is_loading:!0}},computed:Object.assign({},(0,a.Kc)(st,R)),async created(){if(await this.userStore.fetchUser(),this.is_loading=!1,""!==location.hash){const t=new URLSearchParams(location.hash.replace("#",""));if(null!==t.get("status")&&null!==t.get("detail")){const e=parseInt(t.get("status")),s=t.get("detail");this.onOAuthCallbackReceived(e,s),history.replaceState(null,""," ")}}},methods:{async loginNiconicoAccount(){if(!1===this.userStore.is_logged_in)return void this.$message.warning("連携をはじめるには、KonomiTV アカウントにログインしてください。");const t=await ds.fetchAuthorizationURL();if(null===t)return;if(!0===ht.isMobileDevice())return void(location.href=t);const e=window.open(t,"KonomiTV-OAuthPopup",ht.getWindowFeatures());if(null===e)return void this.$message.error("ポップアップウインドウを開けませんでした。");const s=async t=>{if(e.closed)return;if("object"!==ht["typeof"](t.data))return;if("KonomiTV-OAuthPopup"in t.data===!1)return;e&&e.close(),window.removeEventListener("message",s);const i=t.data["KonomiTV-OAuthPopup"]["status"],a=t.data["KonomiTV-OAuthPopup"]["detail"];this.onOAuthCallbackReceived(i,a)};window.addEventListener("message",s)},async onOAuthCallbackReceived(t,e){if(console.log(`NiconicoAuthCallbackAPI: Status: ${t} / Detail: ${e}`),200===t)await this.userStore.fetchUser(!0),this.$message.success("ニコニコアカウントと連携しました。");else if(e.startsWith("Authorization was denied (access_denied)"))this.$message.error("ニコニコアカウントとの連携がキャンセルされました。");else if(e.startsWith("Failed to get access token (HTTP Error ")){const t=e.replace("Failed to get access token ","");this.$message.error(`アクセストークンの取得に失敗しました。${t}`)}else if(e.startsWith("Failed to get access token (Connection Timeout)"))this.$message.error("アクセストークンの取得に失敗しました。ニコニコで障害が発生している可能性があります。");else if(e.startsWith("Failed to get user information (HTTP Error ")){const t=e.replace("Failed to get user information ","");this.$message.error(`ニコニコアカウントのユーザー情報の取得に失敗しました。${t}`)}else e.startsWith("Failed to get user information (Connection Timeout)")?this.$message.error("ニコニコアカウントのユーザー情報の取得に失敗しました。ニコニコで障害が発生している可能性があります。"):this.$message.error(`ニコニコアカウントとの連携に失敗しました。(${e})`)},async logoutNiconicoAccount(){const t=await ds.logoutAccount();!1!==t&&(await this.userStore.fetchUser(!0),this.$message.success("ニコニコアカウントとの連携を解除しました。"))}}}),ms=us,hs=(0,h.Z)(ms,ss,is,!1,null,"ac48731c",null),ps=hs.exports,gs=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("SettingsBase",[e("h2",{staticClass:"settings__heading"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"settings__back-button",attrs:{to:"/settings/"}},[e("Icon",{attrs:{icon:"fluent:arrow-left-12-filled",width:"25px"}})],1),e("Icon",{attrs:{icon:"fluent:server-surface-16-filled",width:"22px"}}),e("span",{staticClass:"ml-2"},[t._v("サーバー設定")])],1),e("div",{staticClass:"settings__content"},[e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("鋭意開発中…")])])])])},vs=[],fs=o["default"].extend({name:"Settings-Server",components:{SettingsBase:ke}}),ws=fs,ys=(0,h.Z)(ws,gs,vs,!1,null,null,null),bs=ys.exports,Cs=s(8236),ks=s(6275),xs=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("SettingsBase",[e("h2",{staticClass:"settings__heading"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"settings__back-button",attrs:{to:"/settings/"}},[e("Icon",{attrs:{icon:"fluent:arrow-left-12-filled",width:"25px"}})],1),e("Icon",{attrs:{icon:"fa-brands:twitter",width:"22px"}}),e("span",{staticClass:"ml-3"},[t._v("Twitter")])],1),e("div",{staticClass:"settings__content",class:{"settings__content--loading":t.is_loading}},[e("div",{staticClass:"twitter-accounts"},[null!==t.userStore.user&&t.userStore.user.twitter_accounts.length>0?e("div",{staticClass:"twitter-accounts__heading"},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:person-board-20-filled",height:"30"}}),t._v("連携中のアカウント ")],1):t._e(),null===t.userStore.user||0===t.userStore.user.twitter_accounts.length?e("div",{staticClass:"twitter-accounts__guide"},[e("Icon",{staticClass:"flex-shrink-0",attrs:{icon:"fa-brands:twitter",width:"45px"}}),e("div",{staticClass:"ml-4"},[e("div",{staticClass:"font-weight-bold text-h6"},[t._v("Twitter アカウントと連携していません")]),e("div",{staticClass:"text--text text--darken-1 text-subtitle-2 mt-1"},[t._v(" Twitter アカウントと連携すると、テレビを見ながら Twitter にツイートしたり、ほかの実況ツイートをリアルタイムで表示できるようになります。 ")])])],1):t._e(),t._l(null!==t.userStore.user?t.userStore.user.twitter_accounts:[],(function(s){return e("div",{key:s.id,staticClass:"twitter-account"},[e("img",{staticClass:"twitter-account__icon",attrs:{src:s.icon_url}}),e("div",{staticClass:"twitter-account__info"},[e("div",{staticClass:"twitter-account__info-name"},[e("span",{staticClass:"twitter-account__info-name-text"},[t._v(t._s(s.name))])]),e("span",{staticClass:"twitter-account__info-screen-name"},[t._v(" @"+t._s(s.screen_name)+" "),!0===s.is_oauth_session?e("span",[t._v("(Legacy Session)")]):t._e()])]),e(j.Z,{staticClass:"twitter-account__logout ml-auto",attrs:{width:"124",height:"52",depressed:""},on:{click:function(e){return t.logoutTwitterAccount(s.screen_name)}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:plug-disconnected-20-filled",height:"24"}}),t._v("連携解除 ")],1)],1)})),e(j.Z,{staticClass:"twitter-account__login",attrs:{color:"secondary","max-width":"250",height:"50",depressed:""},on:{click:function(e){return t.loginTwitterAccountWithPasswordForm()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:plug-connected-20-filled",height:"24"}}),t._v("連携するアカウントを追加 ")],1),e(le.Z,{attrs:{"max-width":"600"},model:{value:t.twitter_password_auth_dialog,callback:function(e){t.twitter_password_auth_dialog=e},expression:"twitter_password_auth_dialog"}},[e(pt.Z,[e(gt.EB,{staticClass:"justify-center pt-6 font-weight-bold"},[t._v("Twitter にログイン")]),e(gt.ZB,{staticClass:"pt-2 pb-0"},[e("p",{staticClass:"mb-1"},[t._v("2023/4/30 以降、Twitter のサードパーティー API の事実上の廃止により、従来のアプリ連携では Twitter にアクセスできなくなりました。")]),e("p",{staticClass:"mb-1"},[t._v("そこで KonomiTV では、代わりにユーザー名とパスワードでログインすることで、これまで通り Twitter 連携ができるようにしています (2要素認証を設定しているアカウントには対応していません) 。")]),e("p",{staticClass:"mb-1"},[t._v("万全は期していますが、非公式な方法のため、使い方次第ではアカウントにペナルティが適用される可能性もあります。自己の責任のもとでご利用ください。")]),e(ft.Z,{ref:"twitter_form",staticClass:"settings__item",on:{submit:function(t){t.preventDefault()}}},[e(yt.Z,{ref:"twitter_screen_name",staticClass:"settings__item-form mt-6",attrs:{outlined:"",label:"ユーザー名 (@ から始まる ID)",placeholder:"screen_name",dense:t.is_form_dense,rules:[t=>!!t||"ユーザー名を入力してください。"]},model:{value:t.twitter_screen_name,callback:function(e){t.twitter_screen_name=e},expression:"twitter_screen_name"}}),e(yt.Z,{staticClass:"settings__item-form",attrs:{outlined:"",label:"パスワード",dense:t.is_form_dense,type:t.twitter_password_showing?"text":"password","append-icon":t.twitter_password_showing?"mdi-eye":"mdi-eye-off",rules:[t=>!!t||"パスワードを入力してください。"]},on:{"click:append":function(e){t.twitter_password_showing=!t.twitter_password_showing}},model:{value:t.twitter_password,callback:function(e){t.twitter_password=e},expression:"twitter_password"}})],1)],1),e(gt.h7,{staticClass:"pt-0 px-6 pb-5"},[e(kt.Z),e(j.Z,{attrs:{color:"text",height:"40",text:""},on:{click:function(e){t.twitter_password_auth_dialog=!1}}},[t._v("キャンセル")]),e(j.Z,{staticClass:"px-4",attrs:{color:"secondary",height:"40"},on:{click:function(e){return t.loginTwitterAccountWithPassword()}}},[t._v("ログイン")])],1)],1)],1),e(j.Z,{staticClass:"twitter-account__login",attrs:{color:"secondary","max-width":"310",height:"50",depressed:""},on:{click:function(e){return t.loginTwitterAccountWithOAuth()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:plug-connected-20-filled",height:"24"}}),t._v("連携するアカウントを追加 (Legacy) ")],1)],2),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"fold_panel_after_sending_tweet"}},[t._v("ツイート送信後にパネルを折りたたむ")]),e("label",{staticClass:"settings__item-label",attrs:{for:"fold_panel_after_sending_tweet"}},[t._v(" この設定をオンにすると、ツイートを送信した後に、パネルが自動で折りたたまれます。"),e("br"),t._v(" ツイートするとき以外はできるだけ映像を大きくして見たい方におすすめです。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"fold_panel_after_sending_tweet",inset:"","hide-details":""},model:{value:t.settingsStore.settings.fold_panel_after_sending_tweet,callback:function(e){t.$set(t.settingsStore.settings,"fold_panel_after_sending_tweet",e)},expression:"settingsStore.settings.fold_panel_after_sending_tweet"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"reset_hashtag_when_program_switches"}},[t._v("番組が切り替わったときにハッシュタグフォームをリセットする")]),e("label",{staticClass:"settings__item-label",attrs:{for:"reset_hashtag_when_program_switches"}},[t._v(" チャンネルを切り替えたときや、視聴中の番組が終了し次の番組の放送が開始されたときに、ハッシュタグフォームをリセットするかを設定します。"),e("br"),t._v(" この設定をオンにしておけば、「誤って前番組のハッシュタグをつけたまま次番組の実況ツイートをしてしまう」といったミスを回避できます。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"reset_hashtag_when_program_switches",inset:"","hide-details":""},model:{value:t.settingsStore.settings.reset_hashtag_when_program_switches,callback:function(e){t.$set(t.settingsStore.settings,"reset_hashtag_when_program_switches",e)},expression:"settingsStore.settings.reset_hashtag_when_program_switches"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"auto_add_watching_channel_hashtag"}},[t._v("視聴中のチャンネルに対応する局タグを自動で追加する")]),e("label",{staticClass:"settings__item-label",attrs:{for:"auto_add_watching_channel_hashtag"}},[t._v(" この設定をオンにすると、視聴中のチャンネルに対応する局タグ (#nhk, #tokyomx など) がハッシュタグフォームに自動で追加されます。"),e("br"),t._v(" 現時点で、局タグは三大首都圏の地上波・BS の一部チャンネル・AT-X にのみ対応しています。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"auto_add_watching_channel_hashtag",inset:"","hide-details":""},model:{value:t.settingsStore.settings.auto_add_watching_channel_hashtag,callback:function(e){t.$set(t.settingsStore.settings,"auto_add_watching_channel_hashtag",e)},expression:"settingsStore.settings.auto_add_watching_channel_hashtag"}})],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("デフォルトで表示される Twitter タブ内のタブ")]),e("div",{staticClass:"settings__item-label"},[t._v(" 視聴画面を開いたときに、パネルの Twitter タブの中で最初に表示されるタブを設定します。"),e("br")]),e(je.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",dense:t.is_form_dense,items:t.twitter_active_tab},model:{value:t.settingsStore.settings.twitter_active_tab,callback:function(e){t.$set(t.settingsStore.settings,"twitter_active_tab",e)},expression:"settingsStore.settings.twitter_active_tab"}})],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("ツイートにつけるハッシュタグの位置")]),e("div",{staticClass:"settings__item-label"},[t._v(" ツイート本文から見て、ハッシュタグをどの位置につけてツイートするかを設定します。"),e("br")]),e(je.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",dense:t.is_form_dense,items:t.tweet_hashtag_position},model:{value:t.settingsStore.settings.tweet_hashtag_position,callback:function(e){t.$set(t.settingsStore.settings,"tweet_hashtag_position",e)},expression:"settingsStore.settings.tweet_hashtag_position"}})],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("ツイートするキャプチャに番組タイトルの透かしを描画する")]),e("div",{staticClass:"settings__item-label"},[t._v(" ツイートするキャプチャに、透かしとして視聴中の番組タイトルを描画するかを設定します。"),e("br"),t._v(" 透かしの描画位置は 左上・右上・左下・右下 から選択できます。"),e("br")]),e(je.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",dense:t.is_form_dense,items:t.tweet_capture_watermark_position},model:{value:t.settingsStore.settings.tweet_capture_watermark_position,callback:function(e){t.$set(t.settingsStore.settings,"tweet_capture_watermark_position",e)},expression:"settingsStore.settings.tweet_capture_watermark_position"}})],1)]),e(Cs.Z,{attrs:{value:t.is_twitter_password_auth_sending,"z-index":"300"}},[e(ks.Z,{attrs:{color:"secondary",indeterminate:"",size:"64"}})],1)],1)},Ss=[];class Os{static async fetchAuthorizationURL(){const t=await G.get("/twitter/auth");return"is_error"in t?(G.showGenericError(t,"Twitter アカウントとの連携用の認証 URL を取得できませんでした。"),null):t.data.authorization_url}static async authWithPassword(t){const e=await G.post("/twitter/password-auth",t);if("is_error"in e){if(e.error.message.startsWith("Failed to authenticate with password")){const t=e.error.message.match(/Message: (.+)\)/)[1];K.error(`ログインに失敗しました。${t}`)}else if(e.error.message.startsWith("Unexpected error occurred while authenticate with password")){const t=e.error.message.match(/Message: (.+)\)/)[1];K.error(`ログインフローの途中で予期せぬエラーが発生しました。${t}`)}else e.error.message.startsWith("Failed to get user information")?K.error("Twitter アカウントのユーザー情報の取得に失敗しました。"):G.showGenericError(e,"Twitter アカウントとの連携に失敗しました。");return!1}return!0}static async logoutAccount(t){const e=await G["delete"](`/twitter/accounts/${t}`);return!("is_error"in e)||(G.showGenericError(e,"Twitter アカウントとの連携を解除できませんでした。"),!1)}static async sendTweet(t,e,s){const i=new FormData;i.append("tweet",e);for(const n of s)i.append("images",n);const a=await G.post(`/twitter/accounts/${t}/tweets`,i,{headers:{"Content-Type":"multipart/form-data"}});return"is_error"in a?a.error.message?Number.isNaN(a.status)?{message:`エラー: ツイートの送信に失敗しました。(${a.error.message})`,is_error:!0}:{message:`エラー: ツイートの送信に失敗しました。(HTTP Error ${a.status} / ${a.error.message})`,is_error:!0}:{message:`エラー: ツイートの送信に失敗しました。(HTTP Error ${a.status})`,is_error:!0}:!0===a.data.is_success?{message:a.data.detail,is_error:!1}:{message:`エラー: ${a.data.detail}`,is_error:!0}}}var Ts=Os,js=o["default"].extend({name:"Settings-Twitter",components:{SettingsBase:ke},data(){return{is_form_dense:ht.isSmartphoneHorizontal(),twitter_active_tab:[{text:"ツイート検索タブ",value:"Search"},{text:"タイムラインタブ",value:"Timeline"},{text:"キャプチャタブ",value:"Capture"}],tweet_hashtag_position:[{text:"ツイート本文の前に追加する",value:"Prepend"},{text:"ツイート本文の後に追加する",value:"Append"},{text:"ツイート本文の前に追加してから改行する",value:"PrependWithLineBreak"},{text:"ツイート本文の後に改行してから追加する",value:"AppendWithLineBreak"}],tweet_capture_watermark_position:[{text:"透かしを描画しない",value:"None"},{text:"透かしをキャプチャの左上に描画する",value:"TopLeft"},{text:"透かしをキャプチャの右上に描画する",value:"TopRight"},{text:"透かしをキャプチャの左下に描画する",value:"BottomLeft"},{text:"透かしをキャプチャの右下に描画する",value:"BottomRight"}],is_loading:!0,is_twitter_password_auth_sending:!1,twitter_password_auth_dialog:!1,twitter_screen_name:"",twitter_password:"",twitter_password_showing:!1}},computed:Object.assign({},(0,a.Kc)(st,R)),async created(){if(await this.userStore.fetchUser(),this.is_loading=!1,""!==location.hash){const t=new URLSearchParams(location.hash.replace("#",""));if(null!==t.get("status")&&null!==t.get("detail")){const e=parseInt(t.get("status")),s=t.get("detail");this.onOAuthCallbackReceived(e,s),history.replaceState(null,""," ")}}},methods:{async loginTwitterAccountWithPasswordForm(){if(!1===this.userStore.is_logged_in)return this.$message.warning("連携をはじめるには、KonomiTV アカウントにログインしてください。"),await ht.sleep(.01),void(this.twitter_password_auth_dialog=!1);this.twitter_password_auth_dialog=!0},async loginTwitterAccountWithPassword(){if(!1===this.$refs.twitter_form.validate())return;this.is_twitter_password_auth_sending=!0;const t=await Ts.authWithPassword({screen_name:this.twitter_screen_name,password:this.twitter_password});if(this.is_twitter_password_auth_sending=!1,!1===t)return;if(await this.userStore.fetchUser(!0),null===this.userStore.user)return void this.$message.error("アカウント情報を取得できませんでした。");const e=[...this.userStore.user.twitter_accounts].sort(((t,e)=>t.updated_ate.updated_at?-1:0))[0];this.$message.success(`Twitter @${e.screen_name} と連携しました。`),this.$refs.twitter_form.reset(),this.twitter_password_auth_dialog=!1},async loginTwitterAccountWithOAuth(){if(!1===this.userStore.is_logged_in)return void this.$message.warning("連携をはじめるには、KonomiTV アカウントにログインしてください。");const t=await Ts.fetchAuthorizationURL();if(null===t)return;if(!0===ht.isMobileDevice())return void(location.href=t);const e=window.open(t,"KonomiTV-OAuthPopup",ht.getWindowFeatures());if(null===e)return void this.$message.error("ポップアップウインドウを開けませんでした。");const s=async t=>{if(e.closed)return;if("object"!==ht["typeof"](t.data))return;if("KonomiTV-OAuthPopup"in t.data===!1)return;e&&e.close(),window.removeEventListener("message",s);const i=t.data["KonomiTV-OAuthPopup"]["status"],a=t.data["KonomiTV-OAuthPopup"]["detail"];this.onOAuthCallbackReceived(i,a)};window.addEventListener("message",s)},async onOAuthCallbackReceived(t,e){if(console.log(`TwitterAuthCallbackAPI: Status: ${t} / Detail: ${e}`),200!==t)return void(e.startsWith("Authorization was denied by user")?this.$message.error("Twitter アカウントとの連携がキャンセルされました。"):e.startsWith("Failed to get access token")?this.$message.error("アクセストークンの取得に失敗しました。"):e.startsWith("Failed to get user information")?this.$message.error("Twitter アカウントのユーザー情報の取得に失敗しました。"):this.$message.error(`Twitter アカウントとの連携に失敗しました。(${e})`));if(await this.userStore.fetchUser(!0),null===this.userStore.user)return void this.$message.error("アカウント情報を取得できませんでした。");const s=[...this.userStore.user.twitter_accounts].sort(((t,e)=>t.updated_ate.updated_at?-1:0))[0];this.$message.success(`Twitter @${s.screen_name} と連携しました。`)},async logoutTwitterAccount(t){const e=await Ts.logoutAccount(t);!1!==e&&(await this.userStore.fetchUser(!0),this.$message.success(`Twitter @${t} との連携を解除しました。`))}}}),Is=js,Ps=(0,h.Z)(Is,xs,Ss,!1,null,"ea90430c",null),Zs=Ps.exports,zs=s(3333),$s=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"route-container"},[e("Header"),e("main",[e("Navigation"),e("div",{staticClass:"channels-container channels-container--home",class:{"channels-container--loading":t.is_loading}},[e("v-tabs-fix",{staticClass:"channels-tab",attrs:{centered:""},model:{value:t.tab,callback:function(e){t.tab=e},expression:"tab"}},t._l(Array.from(t.channelsStore.channels_list_with_pinned),(function([s]){return e(zs.Z,{key:s,staticClass:"channels-tab__item"},[t._v(" "+t._s(s)+" ")])})),1),e("v-tabs-items-fix",{staticClass:"channels-list",model:{value:t.tab,callback:function(e){t.tab=e},expression:"tab"}},t._l(Array.from(t.channelsStore.channels_list_with_pinned),(function([s,i]){return e("v-tab-item-fix",{key:s,staticClass:"channels-tabitem"},[e("div",{staticClass:"channels",class:`channels--tab-${s} channels--length-${i.length}`},[t._l(i,(function(s){return e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],key:s.id,staticClass:"channel",attrs:{to:`/tv/watch/${s.channel_id}`}},[e("div",{staticClass:"channel__broadcaster"},[e("img",{staticClass:"channel__broadcaster-icon",attrs:{src:`${t.Utils.api_base_url}/channels/${s.channel_id}/logo`}}),e("div",{staticClass:"channel__broadcaster-content"},[e("span",{staticClass:"channel__broadcaster-name"},[t._v("Ch: "+t._s(s.channel_number)+" "+t._s(s.channel_name))]),e("div",{staticClass:"channel__broadcaster-status"},[e("div",{staticClass:"channel__broadcaster-status-force",class:`channel__broadcaster-status-force--${t.ChannelUtils.getChannelForceType(s.channel_force)}`},[e("Icon",{attrs:{icon:"fa-solid:fire-alt",height:"12px"}}),e("span",{staticClass:"ml-1"},[t._v("勢い:")]),e("span",{staticClass:"ml-1"},[t._v(t._s(t.ProgramUtils.getAttribute(s,"channel_force","--")))]),e("span",{staticStyle:{"margin-left":"3px"}},[t._v(" コメ/分")])],1),e("div",{staticClass:"channel__broadcaster-status-viewers ml-4"},[e("Icon",{attrs:{icon:"fa-solid:eye",height:"14px"}}),e("span",{staticClass:"ml-1"},[t._v("視聴数:")]),e("span",{staticClass:"ml-1"},[t._v(t._s(s.viewers))])],1)])]),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip",value:t.isPinnedChannel(s.channel_id)?"ピン留めを外す":"ピン留めする",expression:"isPinnedChannel(channel.channel_id) ? 'ピン留めを外す' : 'ピン留めする'"}],staticClass:"channel__broadcaster-pin",class:{"channel__broadcaster-pin--pinned":t.isPinnedChannel(s.channel_id)},on:{click:function(e){e.preventDefault(),e.stopPropagation(),t.isPinnedChannel(s.channel_id)?t.removePinnedChannel(s.channel_id):t.addPinnedChannel(s.channel_id)},mousedown:function(t){t.preventDefault(),t.stopPropagation()}}},[e("Icon",{attrs:{icon:"fluent:pin-20-filled",width:"24px"}})],1)]),e("div",{staticClass:"channel__program-present"},[e("div",{staticClass:"channel__program-present-title-wrapper"},[e("span",{staticClass:"channel__program-present-title",domProps:{innerHTML:t._s(t.ProgramUtils.decorateProgramInfo(s.program_present,"title"))}}),e("span",{staticClass:"channel__program-present-time"},[t._v(t._s(t.ProgramUtils.getProgramTime(s.program_present)))])]),e("span",{staticClass:"channel__program-present-description",domProps:{innerHTML:t._s(t.ProgramUtils.decorateProgramInfo(s.program_present,"description"))}})]),e(kt.Z),e("div",{staticClass:"channel__program-following"},[e("div",{staticClass:"channel__program-following-title"},[e("span",{staticClass:"channel__program-following-title-decorate"},[t._v("NEXT")]),e("Icon",{staticClass:"channel__program-following-title-icon",attrs:{icon:"fluent:fast-forward-20-filled",width:"16px"}}),e("span",{staticClass:"channel__program-following-title-text",domProps:{innerHTML:t._s(t.ProgramUtils.decorateProgramInfo(s.program_following,"title"))}})],1),e("span",{staticClass:"channel__program-following-time"},[t._v(t._s(t.ProgramUtils.getProgramTime(s.program_following)))])]),e("div",{staticClass:"channel__progressbar"},[e("div",{staticClass:"channel__progressbar-progress",style:`width:${t.ProgramUtils.getProgramProgress(s.program_present)}%;`})])],1)})),"ピン留め"===s&&0===i.length?e("div",{staticClass:"pinned-container d-flex justify-center align-center w-100"},[e("div",{staticClass:"d-flex justify-center align-center flex-column"},[e("h2",[t._v("ピン留めされているチャンネルが"),e("br"),t._v("ありません。")]),e("div",{staticClass:"mt-4 text--text text--darken-1"},[t._v("各チャンネルの "),e("Icon",{staticStyle:{position:"relative",bottom:"-5px"},attrs:{icon:"fluent:pin-20-filled",width:"22px"}}),t._v(" アイコンから、よくみる"),e("br"),t._v("チャンネルをこのタブにピン留めできます。")],1),e("div",{staticClass:"mt-2 text--text text--darken-1"},[t._v("チャンネルをピン留めすると、"),e("br"),t._v("このタブが最初に表示されます。")])])]):t._e()],2)])})),1)],1)],1)],1)},As=[];const Ds={id:"NID0-SID0-EID0",network_id:0,service_id:0,event_id:0,channel_id:"gr000",title:"取得中…",description:"取得中…",detail:{},start_time:"2000-01-01T00:00:00+09:00",end_time:"2000-01-01T00:00:00+09:00",duration:0,is_free:!0,genre:[],video_type:"映像1080i(1125i)、アスペクト比16:9 パンベクトルなし",video_codec:"mpeg2",video_resolution:"1080i",primary_audio_type:"2/0モード(ステレオ)",primary_audio_language:"日本語",primary_audio_sampling_rate:"48kHz",secondary_audio_type:null,secondary_audio_language:null,secondary_audio_sampling_rate:null},Bs={id:"NID0-SID0",network_id:0,service_id:0,transport_stream_id:null,remocon_id:null,channel_id:"gr000",channel_number:"---",channel_name:"取得中…",channel_type:"GR",channel_force:null,channel_comment:null,is_subchannel:!1,is_radiochannel:!1,is_display:!0,viewers:0,program_present:Ds,program_following:Ds};class Ns{static async fetchAll(){const t=await G.get("/channels");return"is_error"in t?(G.showGenericError(t,"チャンネル情報を取得できませんでした。"),null):t.data}static async fetch(t){const e=await G.get(`/channels/${t}`);return"is_error"in e?(G.showGenericError(e,"チャンネル情報を取得できませんでした。"),null):e.data}static async fetchJikkyoSession(t){const e=await G.get(`/channels/${t}/jikkyo`);return"is_error"in e?(G.showGenericError(e,"ニコニコ実況のセッション情報を取得できませんでした。"),null):e.data}}var Ks=Ns;const Ls=(0,a.Q_)("channels",{state:()=>({channel_id:"gr000",channels_list:{GR:[],BS:[],CS:[],CATV:[],SKY:[],STARDIGIO:[]},is_channels_list_initial_updated:!1,last_updated_at:0}),getters:{is_showing_live(){return"gr000"!==this.channel_id},channel(){const t=this.channels_list[D.getChannelType(this.channel_id)];if(void 0===t||0===t.length)return{previous:Bs,current:Bs,next:Bs};const e=t.findIndex((t=>t.channel_id===this.channel_id));if(-1===e){const t=Object.assign(Object.assign({},Bs.program_present),{channel_id:"gr999",title:"チャンネル情報取得エラー",description:"このチャンネル ID のチャンネル情報は存在しません。"}),e=Object.assign(Object.assign({},Bs),{channel_id:"gr999",channel_name:"ERROR",program_present:t,program_following:t});return{previous:e,current:e,next:e}}const s=(()=>{let s=e-1;while(t.length){if(s<=-1&&(s=t.length-1),t[s].is_display)return s;s--}return 0})(),i=(()=>{let s=e+1;while(t.length){if(s>=t.length&&(s=0),t[s].is_display)return s;s++}return 0})();return{previous:t[s],current:t[e],next:t[i]}},channels_list_with_pinned(){var t,e,s,i,a,n,r;const o=st(),l=new Map;if(l.set("ピン留め",[]),l.set("地デジ",[]),!1===this.is_channels_list_initial_updated)return l;l.set("BS",[]),l.set("CS",[]),l.set("CATV",[]),l.set("SKY",[]),l.set("StarDigio",[]);for(const[c,_]of Object.entries(this.channels_list))for(const d of _)if(!1!==d.is_display)switch(o.settings.pinned_channel_ids.includes(d.channel_id)&&(null===(t=l.get("ピン留め"))||void 0===t||t.push(d)),d.channel_type){case"GR":null===(e=l.get("地デジ"))||void 0===e||e.push(d);break;case"BS":null===(s=l.get("BS"))||void 0===s||s.push(d);break;case"CS":null===(i=l.get("CS"))||void 0===i||i.push(d);break;case"CATV":null===(a=l.get("CATV"))||void 0===a||a.push(d);break;case"SKY":null===(n=l.get("SKY"))||void 0===n||n.push(d);break;case"STARDIGIO":null===(r=l.get("StarDigio"))||void 0===r||r.push(d);break}for(const c of[...l.get("ピン留め")]){const t=o.settings.pinned_channel_ids.indexOf(c.channel_id);l.get("ピン留め")[t]=c}for(const[c,_]of l)"ピン留め"!==c&&0===_.length&&l.delete(c);return 1===l.size&&l.has("ピン留め")&&l.delete("ピン留め"),l},channels_list_with_pinned_for_watch(){var t;const e=new Map([...this.channels_list_with_pinned]);return 0===(null===(t=e.get("ピン留め"))||void 0===t?void 0:t.length)&&e.delete("ピン留め"),e}},actions:{getChannel(t){var e;const s=this.channels_list[D.getChannelType(t)];return void 0===s?null:null!==(e=s.find((e=>e.channel_id===t)))&&void 0!==e?e:null},getChannelByRemoconID(t,e){const s=this.channels_list[t],i=s.find((t=>t.remocon_id===e));return null!==i&&void 0!==i?i:null},updateChannel(t,e){const s=D.getChannelType(t);if(void 0===this.channels_list[s])return null;const i=this.channels_list[s].findIndex((e=>e.channel_id===t));-1!==i&&o["default"].set(this.channels_list[s],i,e)},async update(t=!1){const e=async()=>{const t=await Ks.fetchAll();null!==t&&(this.channels_list=t,this.is_channels_list_initial_updated=!0,this.last_updated_at=ht.time())};!0!==this.is_channels_list_initial_updated||!1!==t?await e():ht.time()-this.last_updated_at>60&&e()}}});var Es=Ls,Hs=o["default"].extend({name:"TV-Home",components:{Header:It,Navigation:Rt},data(){return{Utils:ht,ChannelUtils:D,ProgramUtils:mt,tab:null,is_loading:!0,interval_ids:[]}},computed:Object.assign({},(0,a.Kc)(Es,st)),async created(){0===this.settingsStore.settings.pinned_channel_ids.length&&(this.tab=1);const t=60-(new Date).getSeconds();this.interval_ids.push(window.setTimeout((()=>{this.channelsStore.update(!0),this.interval_ids.push(window.setInterval((()=>this.channelsStore.update(!0)),3e4))}),1e3*t)),await this.channelsStore.update(),await ht.sleep(.01),0===this.channelsStore.channels_list_with_pinned.get("ピン留め").length&&(this.tab=1),this.is_loading=!1},beforeDestroy(){for(const t of this.interval_ids)window.clearInterval(t)},methods:{addPinnedChannel(t){this.settingsStore.settings.pinned_channel_ids.push(t);const e=this.channelsStore.getChannel(t);this.$message.show(`${e.channel_name}をピン留めしました。`)},removePinnedChannel(t){this.settingsStore.settings.pinned_channel_ids.splice(this.settingsStore.settings.pinned_channel_ids.indexOf(t),1),0===this.channelsStore.channels_list_with_pinned.get("ピン留め").length&&(this.tab=1);const e=this.channelsStore.getChannel(t);this.$message.show(`${e.channel_name}のピン留めを外しました。`)},isPinnedChannel(t){return this.settingsStore.settings.pinned_channel_ids.includes(t)}}}),Ms=Hs,Us=(0,h.Z)(Ms,$s,As,!1,null,"5395b00e",null),Vs=Us.exports,Rs=s(4437),Fs=s(5294),Gs=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"route-container"},[e("main",{staticClass:"watch-container",class:{"watch-container--control-display":t.is_control_display,"watch-container--panel-display":!!t.Utils.isSmartphoneVertical()||t.is_panel_display,"watch-container--fullscreen":t.is_fullscreen}},[e("nav",{staticClass:"watch-navigation",on:{mousemove:function(e){return t.controlDisplayTimer(e)},touchmove:function(e){return t.controlDisplayTimer(e)},click:function(e){return t.controlDisplayTimer(e)}}},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"watch-navigation__icon",attrs:{to:"/tv/"}},[e("img",{staticClass:"watch-navigation__icon-image",attrs:{src:"/assets/images/icon.svg",width:"23px"}})]),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.right",value:"テレビをみる",expression:"'テレビをみる'",modifiers:{right:!0}}],staticClass:"watch-navigation__link",attrs:{"active-class":"watch-navigation__link--active",to:"/tv/"}},[e("Icon",{staticClass:"watch-navigation__link-icon",attrs:{icon:"fluent:tv-20-regular",width:"26px"}})],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.right",value:"ビデオをみる",expression:"'ビデオをみる'",modifiers:{right:!0}}],staticClass:"watch-navigation__link",attrs:{"active-class":"watch-navigation__link--active",to:"/videos/"}},[e("Icon",{staticClass:"watch-navigation__link-icon",attrs:{icon:"fluent:movies-and-tv-20-regular",width:"26px"}})],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.right",value:"番組表",expression:"'番組表'",modifiers:{right:!0}}],staticClass:"watch-navigation__link",attrs:{"active-class":"watch-navigation__link--active",to:"/timetable/"}},[e("Icon",{staticClass:"watch-navigation__link-icon",attrs:{icon:"fluent:calendar-ltr-20-regular",width:"26px"}})],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.right",value:"録画予約",expression:"'録画予約'",modifiers:{right:!0}}],staticClass:"watch-navigation__link",attrs:{"active-class":"watch-navigation__link--active",to:"/reserves/"}},[e("Icon",{staticClass:"watch-navigation__link-icon",staticStyle:{padding:"0.5px"},attrs:{icon:"fluent:timer-16-regular",width:"26px"}})],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.right",value:"マイリスト",expression:"'マイリスト'",modifiers:{right:!0}}],staticClass:"watch-navigation__link",attrs:{"active-class":"watch-navigation__link--active",to:"/mylist/"}},[e("Icon",{staticClass:"watch-navigation__link-icon",attrs:{icon:"ic:round-playlist-play",width:"26px"}})],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.right",value:"キャプチャ",expression:"'キャプチャ'",modifiers:{right:!0}}],staticClass:"watch-navigation__link",attrs:{"active-class":"watch-navigation__link--active",to:"/captures/"}},[e("Icon",{staticClass:"watch-navigation__link-icon",attrs:{icon:"fluent:image-multiple-24-regular",width:"26px"}})],1),e(kt.Z),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.right",value:"設定",expression:"'設定'",modifiers:{right:!0}}],staticClass:"watch-navigation__link",attrs:{"active-class":"watch-navigation__link--active",to:"/settings/"}},[e("Icon",{staticClass:"watch-navigation__link-icon",attrs:{icon:"fluent:settings-20-regular",width:"26px"}})],1)],1),e("div",{staticClass:"watch-content",on:{mousemove:function(e){return t.controlDisplayTimer(e,!0)},touchmove:function(e){return t.controlDisplayTimer(e,!0)},click:function(e){return t.controlDisplayTimer(e,!0)}}},[e("header",{staticClass:"watch-header"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"watch-header__back-icon",attrs:{to:"/tv/"}},[e("Icon",{attrs:{icon:"fluent:arrow-left-12-filled",width:"25px"}})],1),e("img",{staticClass:"watch-header__broadcaster",attrs:{src:`${t.Utils.api_base_url}/channels/${t.channelsStore.channel_id}/logo`}}),e("span",{staticClass:"watch-header__program-title",domProps:{innerHTML:t._s(t.ProgramUtils.decorateProgramInfo(t.channelsStore.channel.current.program_present,"title"))}}),e("span",{staticClass:"watch-header__program-time"},[t._v(" "+t._s(t.ProgramUtils.getProgramTime(t.channelsStore.channel.current.program_present,!0))+" ")]),e(kt.Z),e("span",{staticClass:"watch-header__now"},[t._v(t._s(t.time))])],1),e("div",{staticClass:"watch-player",class:{"watch-player--loading":t.is_loading,"watch-player--virtual-keyboard-display":t.is_virtual_keyboard_display&&t.Utils.hasActiveElementClass("dplayer-comment-input")}},[e("div",{staticClass:"watch-player__background-wrapper"},[e("div",{staticClass:"watch-player__background",class:{"watch-player__background--display":t.is_background_display},style:{backgroundImage:`url(${t.background_url})`}},[e("img",{staticClass:"watch-player__background-logo",attrs:{src:"/assets/images/logo.svg"}})])]),e(ks.Z,{staticClass:"watch-player__buffering",class:{"watch-player__buffering--display":t.is_video_buffering&&(t.is_loading||null!==t.player&&!t.player.video.paused)},attrs:{indeterminate:"",size:"60",width:"6"}}),e("div",{staticClass:"watch-player__dplayer"}),e("div",{staticClass:"watch-player__button",on:{mousemove:function(e){return t.controlDisplayTimer(e)},touchmove:function(e){return t.controlDisplayTimer(e)},click:function(e){return t.controlDisplayTimer(e)}}},[e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.top",value:"前のチャンネル",expression:"'前のチャンネル'",modifiers:{top:!0}}],staticClass:"switch-button switch-button-up",on:{click:function(e){t.is_zapping=!0,t.$router.push({path:`/tv/watch/${t.channelsStore.channel.previous.channel_id}`})}}},[e("Icon",{staticClass:"switch-button-icon",attrs:{icon:"fluent:ios-arrow-left-24-filled",width:"32px",rotate:"1"}})],1),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"switch-button switch-button-panel switch-button-panel--open",on:{click:function(e){t.is_panel_display=!t.is_panel_display}}},[e("Icon",{staticClass:"switch-button-icon",attrs:{icon:"fluent:navigation-16-filled",width:"32px"}})],1),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.bottom",value:"次のチャンネル",expression:"'次のチャンネル'",modifiers:{bottom:!0}}],staticClass:"switch-button switch-button-down",on:{click:function(e){t.is_zapping=!0,t.$router.push({path:`/tv/watch/${t.channelsStore.channel.next.channel_id}`})}}},[e("Icon",{staticClass:"switch-button-icon",attrs:{icon:"fluent:ios-arrow-right-24-filled",width:"33px",rotate:"1"}})],1)])],1)]),e("div",{staticClass:"watch-panel",on:{mousemove:function(e){return t.controlDisplayTimer(e)}}},[e("div",{staticClass:"watch-panel__header"},[e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"panel-close-button",on:{click:function(e){t.is_panel_display=!1}}},[e("Icon",{staticClass:"panel-close-button__icon",attrs:{icon:"akar-icons:chevron-right",width:"25px"}}),e("span",{staticClass:"panel-close-button__text"},[t._v("閉じる")])],1),e(kt.Z),e("div",{staticClass:"panel-broadcaster"},[e("img",{staticClass:"panel-broadcaster__icon",attrs:{src:`${t.Utils.api_base_url}/channels/${t.channelsStore.channel_id}/logo`}}),e("div",{staticClass:"panel-broadcaster__number"},[t._v(t._s(t.channelsStore.channel.current.channel_number))]),e("div",{staticClass:"panel-broadcaster__name"},[t._v(t._s(t.channelsStore.channel.current.channel_name))])])],1),e("div",{staticClass:"watch-panel__content-container"},[e("Program",{staticClass:"watch-panel__content",class:{"watch-panel__content--active":"Program"===t.tv_panel_active_tab}}),e("Channel",{staticClass:"watch-panel__content",class:{"watch-panel__content--active":"Channel"===t.tv_panel_active_tab}}),e("Comment",{ref:"Comment",staticClass:"watch-panel__content",class:{"watch-panel__content--active":"Comment"===t.tv_panel_active_tab},attrs:{channel:t.channelsStore.channel.current,player:t.player}}),e("Twitter",{ref:"Twitter",staticClass:"watch-panel__content",class:{"watch-panel__content--active":"Twitter"===t.tv_panel_active_tab},attrs:{player:t.player,is_virtual_keyboard_display:t.is_virtual_keyboard_display},on:{panel_folding_requested:function(e){t.is_panel_display=!1}}})],1),e("div",{staticClass:"watch-panel__navigation"},[e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"panel-navigation-button",class:{"panel-navigation-button--active":"Program"===t.tv_panel_active_tab},on:{click:function(e){t.tv_panel_active_tab="Program"}}},[e("Icon",{staticClass:"panel-navigation-button__icon",attrs:{icon:"fa-solid:info-circle",width:"33px"}}),e("span",{staticClass:"panel-navigation-button__text"},[t._v("番組情報")])],1),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"panel-navigation-button",class:{"panel-navigation-button--active":"Channel"===t.tv_panel_active_tab},on:{click:function(e){t.tv_panel_active_tab="Channel"}}},[e("Icon",{staticClass:"panel-navigation-button__icon",attrs:{icon:"fa-solid:broadcast-tower",width:"34px"}}),e("span",{staticClass:"panel-navigation-button__text"},[t._v("チャンネル")])],1),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"panel-navigation-button",class:{"panel-navigation-button--active":"Comment"===t.tv_panel_active_tab},on:{click:function(e){t.tv_panel_active_tab="Comment"}}},[e("Icon",{staticClass:"panel-navigation-button__icon",attrs:{icon:"bi:chat-left-text-fill",width:"29px"}}),e("span",{staticClass:"panel-navigation-button__text"},[t._v("コメント")])],1),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"panel-navigation-button",class:{"panel-navigation-button--active":"Twitter"===t.tv_panel_active_tab},on:{click:function(e){t.tv_panel_active_tab="Twitter"}}},[e("Icon",{staticClass:"panel-navigation-button__icon",attrs:{icon:"fa-brands:twitter",width:"34px"}}),e("span",{staticClass:"panel-navigation-button__text"},[t._v("Twitter")])],1)])])]),e(le.Z,{attrs:{"max-width":"1050",transition:"slide-y-transition"},model:{value:t.shortcut_key_modal,callback:function(e){t.shortcut_key_modal=e},expression:"shortcut_key_modal"}},[e(pt.Z,[e(gt.EB,{staticClass:"px-5 pt-4 pb-3 d-flex align-center font-weight-bold"},[e("Icon",{attrs:{icon:"fluent:keyboard-20-filled",height:"28px"}}),e("span",{staticClass:"ml-3"},[t._v("キーボードショートカット")]),e(kt.Z),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"d-flex align-center rounded-circle cursor-pointer px-2 py-2",on:{click:function(e){t.shortcut_key_modal=!1}}},[e("Icon",{attrs:{icon:"fluent:dismiss-12-filled",width:"23px",height:"23px"}})],1)],1),e("div",{staticClass:"px-5 pb-4"},[e(Fs.Z,t._l(t.shortcut_key_list,(function(s,i){return e(Rs.Z,{key:i,attrs:{cols:"6"}},t._l(s,(function(s){return e("div",{key:s.name,staticClass:"mt-3"},[e("div",{staticClass:"text-subtitle-1 d-flex align-center font-weight-bold"},[e("Icon",{attrs:{icon:s.icon,height:s.icon_height}}),e("span",{staticClass:"ml-2"},[t._v(t._s(s.name))])],1),t._l(s.shortcuts,(function(s){return e("div",{key:s.name,staticClass:"mt-3"},[e("div",{staticClass:"text-subtitle-2 mt-2 d-flex align-center font-weight-medium"},[e("span",{staticClass:"mr-2",domProps:{innerHTML:t._s(s.name)}}),e("div",{staticClass:"ml-auto d-flex align-center flex-shrink-0"},t._l(s.keys,(function(i,a){return e("div",{key:i.name,staticClass:"ml-auto d-flex align-center"},[e("span",{staticClass:"shortcut-key"},[t._l(i.name.split(";"),(function(t){return e("Icon",{directives:[{name:"show",rawName:"v-show",value:!0===i.icon,expression:"key.icon === true"}],key:t,attrs:{icon:t,height:"18px"}})})),!1===i.icon?e("span",{domProps:{innerHTML:t._s(i.name)}}):t._e()],2),a{var t;null===(t=this.watch_session)||void 0===t||t.send(JSON.stringify({type:"startWatching",data:{reconnect:!1}}))}),{signal:this.abort_controller.signal}),this.watch_session.addEventListener("close",(async e=>{!0!==t&&(console.error(`[LiveCommentManager][WatchSession] Connection closed. (Code: ${e.code})`),this.player.notice(`ニコニコ実況との接続が切断されました。(Code: ${e.code})`),await ht.sleep(10),await this.reconnect())}),{signal:this.abort_controller.signal}),this.watch_session.addEventListener("message",(async e=>{const s=JSON.parse(e.data);switch(s.type){case"seat":if(null!==this.keep_seat_interval_id)break;this.keep_seat_interval_id=window.setInterval((()=>{var t;this.watch_session&&this.watch_session.readyState===WebSocket.OPEN?this.watch_session.send(JSON.stringify({type:"keepSeat"})):window.clearInterval(null!==(t=this.keep_seat_interval_id)&&void 0!==t?t:0)}),1e3*s.data.keepIntervalSec);break;case"ping":this.watch_session.send(JSON.stringify({type:"pong"}));break;case"error":{if("COMMENT_POST_NOT_ALLOWED"===s.data.code||"INVALID_MESSAGE"===s.data.code)break;let t=`ニコニコ実況でエラーが発生しています。(Code: ${s.data.code})`;switch(s.data.code){case"CONNECT_ERROR":t="ニコニコ実況のコメントサーバーに接続できません。";break;case"CONTENT_NOT_READY":t="ニコニコ実況が配信できない状態です。";break;case"NO_THREAD_AVAILABLE":t="ニコニコ実況のコメントスレッドを取得できません。";break;case"NO_ROOM_AVAILABLE":t="ニコニコ実況のコメント部屋を取得できません。";break;case"NO_PERMISSION":t="ニコニコ実況の API にアクセスする権限がありません。";break;case"NOT_ON_AIR":t="ニコニコ実況が放送中ではありません。";break;case"BROADCAST_NOT_FOUND":t="ニコニコ実況の配信情報を取得できません。";break;case"INTERNAL_SERVERERROR":t="ニコニコ実況でサーバーエラーが発生しています。";break}console.error(`[LiveCommentManager][WatchSession] Error occurred. (Code: ${s.data.code})`),this.player.notice(t),await ht.sleep(5),await this.reconnect();break}case"reconnect":await this.reconnect();break;case"disconnect":{t=!0;let e=`ニコニコ実況との接続が切断されました。(${s.data.reason})`;switch(s.data.reason){case"TAKEOVER":e="ニコニコ実況の番組から追い出されました。";break;case"NO_PERMISSION":e="ニコニコ実況の番組の座席を取得できませんでした。";break;case"END_PROGRAM":e="ニコニコ実況がリセットされたか、コミュニティの番組が終了しました。";break;case"PING_TIMEOUT":e="コメントサーバーとの接続生存確認に失敗しました。";break;case"TOO_MANY_CONNECTIONS":e="ニコニコ実況の同一ユーザからの接続数上限を越えています。";break;case"TOO_MANY_WATCHINGS":e="ニコニコ実況の同一ユーザからの視聴番組数上限を越えています。";break;case"CROWDED":e="ニコニコ実況の番組が満席です。";break;case"MAINTENANCE_IN":e="ニコニコ実況はメンテナンス中です。";break;case"SERVICE_TEMPORARILY_UNAVAILABLE":e="ニコニコ実況で一時的にサーバーエラーが発生しています。";break}console.error(`[LiveCommentManager][WatchSession] Disconnected. (Reason: ${s.data.reason})`),this.player.notice(e),await ht.sleep(5),await this.reconnect();break}}}),{signal:this.abort_controller.signal}),new Promise((t=>{this.watch_session.addEventListener("message",(async e=>{const s=JSON.parse(e.data);if("room"===s.type)return this.vpos_base_timestamp=rt()(s.data.vposBaseTime).valueOf(),console.log(`[LiveCommentManager][WatchSession] Connected.\nThread ID: ${s.data.threadId}\n`),t({is_success:!0,detail:"視聴セッションを取得しました。",message_server_url:s.data.messageServer.uri,thread_id:s.data.threadId,your_post_key:s.data.yourPostKey?s.data.yourPostKey:null})}),{signal:this.abort_controller.signal})})))}initCommentSession(t){const e=[];let s=!1;this.comment_session=new WebSocket(t.message_server_url),this.comment_session.addEventListener("open",(()=>{this.comment_session.send(JSON.stringify([{ping:{content:"rs:0"}},{ping:{content:"ps:0"}},{thread:{version:"20061206",thread:t.thread_id,threadkey:t.your_post_key,user_id:"",res_from:-50}},{ping:{content:"pf:0"}},{ping:{content:"rf:0"}}]))}),{signal:this.abort_controller.signal}),this.comment_session.addEventListener("message",(async t=>{const i=JSON.parse(t.data);if(void 0!==i.thread&&0!==i.thread.resultcode)return void console.error(`[LiveCommentManager][CommentSession] Connection failed. (Code: ${i.thread.resultcode})`);if(void 0!==i.ping&&"rf:0"===i.ping.content)return s=!0,void this.on_initial_comments_received(e);const a=i.chat;if(void 0===a||void 0===a.content||""===a.content||a.yourpost&&1===a.yourpost)return;const{color:n,position:r,size:o}=it.parseCommentCommand(a.mail);if(it.isMutedComment(a.content,a.user_id,n,r,o))return;const l={id:a.no,text:a.content,time:rt()(1e3*a.date).format("HH:mm:ss"),user_id:a.user_id,my_post:!1};if(!1===s)return void e.push(l);let c=0;this.player.video.buffered.length>=1&&(c=this.player.video.buffered.end(0));const _=c-this.player.video.currentTime;await ht.sleep(_),this.on_comment_received(l),!1===this.player.video.paused&&this.player.danmaku.draw({text:a.content,color:n,type:r,size:o})}),{signal:this.abort_controller.signal})}sendComment(t){const e={"#FFEAEA":"white","#F02840":"red","#FD7E80":"pink","#FDA708":"orange","#FFE133":"yellow","#64DD17":"green","#00D4F5":"cyan","#4763FF":"blue"},s={top:"ue",right:"naka",bottom:"shita"},i=Math.round((rt()().valueOf()-this.vpos_base_timestamp)/10);if(null===this.watch_session||this.watch_session.readyState!==WebSocket.OPEN)return console.error("[LiveCommentManager][WatchSession] Comment sending failed. (Connection is not established.)"),void t.error("コメントの送信に失敗しました。WebSocket 接続が確立されていません。");this.watch_session.send(JSON.stringify({type:"postComment",data:{text:t.data.text,color:e[t.data.color.toUpperCase()],position:s[t.data.type],size:t.data.size,vpos:i,isAnonymous:!0}}));const a=new AbortController;this.watch_session.addEventListener("message",(e=>{const s=JSON.parse(e.data);switch(s.type){case"postCommentResult":t.success(),a.abort();break;case"error":{let e=`コメントの送信に失敗しました。(${s.data.code})`;switch(s.data.code){case"COMMENT_POST_NOT_ALLOWED":e="コメントが許可されていません。";break;case"INVALID_MESSAGE":e="コメント内容が無効です。";break}console.error(`[LiveCommentManager][WatchSession] Comment sending failed. (Code: ${s.data.code})`),t.error(e),a.abort();break}}}),{signal:a.signal})}async reconnect(){console.warn("[LiveCommentManager][WatchSession] Reconnecting..."),this.player.notice("ニコニコ実況に再接続しています…"),this.destroy();const t=await this.initSession();!1===t.is_success&&(console.error("[LiveCommentManager][WatchSession] Reconnection failed."),this.player.notice(t.detail))}destroy(){this.abort_controller.abort(),this.abort_controller=new AbortController,null!==this.watch_session&&(this.watch_session.close(),this.watch_session=null),null!==this.comment_session&&(this.comment_session.close(),this.comment_session=null),window.clearInterval(this.keep_seat_interval_id),this.keep_seat_interval_id=null,this.vpos_base_timestamp=0,console.log("[LiveCommentManager][WatchSession] Destroyed.")}}var li=oi,ci=o["default"].extend({name:"Panel-CommentTab",components:{CommentMuteSettings:cs},data(){return{Utils:ht,is_manual_scroll:!1,is_auto_scrolling:!1,comment_list:[],comment_list_element:null,is_comment_list_dropdown_display:!1,comment_list_dropdown_top:0,comment_list_dropdown_comment:null,live_comment_manager:null,initialize_failed_message:null,visibilitychange_listener:null,resize_observer:null,resize_observer_element:null,comment_mute_settings_modal:!1}},computed:Object.assign({},(0,a.Kc)(R)),created(){this.userStore.fetchUser()},mounted(){null===this.comment_list_element&&(this.comment_list_element=this.$el.querySelector(".comment-list"));let t=!1;this.comment_list_element.onmousedown=e=>{const s=e.clientX-this.comment_list_element.getBoundingClientRect().left;s>this.comment_list_element.clientWidth&&(t=!0)},this.comment_list_element.onmouseup=e=>{const s=e.clientX-this.comment_list_element.getBoundingClientRect().left;s>this.comment_list_element.clientWidth&&(t=!1)};const e=()=>{t=!0,window.setTimeout((()=>t=!1),100)};let s=!1;this.comment_list_element.ontouchstart=()=>s=!0,this.comment_list_element.ontouchend=()=>s=!1,this.comment_list_element.ontouchmove=()=>!0===s?e():"",this.comment_list_element.onwheel=e,this.comment_list_element.onscroll=async()=>{!1===this.is_auto_scrolling&&!0===t&&(this.is_manual_scroll=!0,await ht.sleep(.1),this.comment_list_element.scrollTop+this.comment_list_element.offsetHeight>this.comment_list_element.scrollHeight-10&&(this.is_manual_scroll=!1))}},beforeDestroy(){this.destroy(),null!==this.resize_observer&&this.resize_observer.unobserve(this.resize_observer_element)},methods:{showCommentListDropdown(t,e){const s=this.$refs.comment_list_wrapper.getBoundingClientRect(),i=76,a=t.currentTarget.getBoundingClientRect();this.comment_list_dropdown_top=a.top-s.top,this.comment_list_dropdown_top+i>s.height&&(this.comment_list_dropdown_top=this.comment_list_dropdown_top-i+a.height),this.comment_list_dropdown_comment=e,this.is_comment_list_dropdown_display=!0},hideCommentListDropdown(){this.is_comment_list_dropdown_display=!1,this.comment_list=this.comment_list.filter((t=>!1===it.isMutedComment(t.text,t.user_id)))},addMutedKeywords(){it.addMutedKeywords(this.comment_list_dropdown_comment.text),this.hideCommentListDropdown()},addMutedNiconicoUserIds(){it.addMutedNiconicoUserIDs(this.comment_list_dropdown_comment.user_id),this.hideCommentListDropdown()},async scrollCommentList(t=!1){if(!0===this.is_comment_list_dropdown_display&&(this.is_manual_scroll=!0),!0!==this.is_manual_scroll){this.is_auto_scrolling=!0;for(let e=0;e<3;e++)await ht.sleep(.01),!0===t?this.comment_list_element.scrollTo({top:this.comment_list_element.scrollHeight,left:0,behavior:"smooth"}):this.comment_list_element.scrollTo(0,this.comment_list_element.scrollHeight);await ht.sleep(.1),this.is_auto_scrolling=!1}},initReserveObserver(){null!==this.resize_observer&&this.resize_observer.unobserve(this.resize_observer_element),this.resize_observer_element=document.querySelector(".watch-player");let t=null;const e=()=>{const e=document.querySelector(".dplayer-video-wrap-aspect"),s=document.querySelector(".dplayer-danmaku");if(null===this.resize_observer_element||null===this.resize_observer_element.clientHeight)return;if(null===e||null===e.clientHeight)return;const i=(this.resize_observer_element.clientHeight-e.clientHeight)/2,a=ht.isSmartphoneVertical()?0:window.matchMedia("(max-height: 450px)").matches?50:66;if(i0===e?t:l(e,t%e),c=l(r,o),_=`${r/c} / ${o/c}`;s.style.transition="none",s.style.setProperty("--comment-area-aspect-ratio",_),s.style.setProperty("--comment-area-vertical-margin",`${n}px`),window.clearTimeout(t),window.setTimeout((()=>{s.style.transition=""}),200)}else s.style.removeProperty("--comment-area-aspect-ratio"),s.style.removeProperty("--comment-area-vertical-margin")};this.resize_observer=new ResizeObserver(e),this.resize_observer.observe(this.resize_observer_element),window.setTimeout(e,600)},async initSession(t,e){this.initReserveObserver();const s=[],i=500;this.live_comment_manager=new li(t,e,(async t=>{this.comment_list.push(...t),this.scrollCommentList()}),(async t=>{"hidden"!==document.visibilityState?(this.comment_list.length>=i&&!1===this.is_manual_scroll&&this.comment_list.splice(0,Math.max(0,this.comment_list.length-i)),this.comment_list.push(t),this.scrollCommentList()):s.push(t)})),this.visibilitychange_listener=()=>{if("visible"===document.visibilityState){const t=this.comment_list.length+s.length;t>=i&&!1===this.is_manual_scroll&&this.comment_list.splice(0,Math.max(0,t-i)),this.comment_list.push(...s),s.length=0,this.scrollCommentList()}},document.addEventListener("visibilitychange",this.visibilitychange_listener);const a=await this.live_comment_manager.initSession();!1===a.is_success&&(this.initialize_failed_message=a.detail)},sendComment(t){null===this.initialize_failed_message?null!==this.userStore.user?null!==this.userStore.user.niconico_user_id?!1!==this.userStore.user.niconico_user_premium||"top"!==t.data.type&&"bottom"!==t.data.type?!1!==this.userStore.user.niconico_user_premium||"big"!==t.data.size?this.live_comment_manager.sendComment(t):t.error("コメントサイズを大きめに設定するには、ニコニコアカウントのプレミアム会員登録が必要です。"):t.error("コメントを上下に固定するには、ニコニコアカウントのプレミアム会員登録が必要です。"):t.error("コメントするには、ニコニコアカウントと連携してください。"):t.error("コメントするには、KonomiTV アカウントにログインしてください。"):t.error(this.initialize_failed_message)},destroy(){null!==this.visibilitychange_listener&&(document.removeEventListener("visibilitychange",this.visibilitychange_listener),this.visibilitychange_listener=null),null!==this.live_comment_manager&&(this.live_comment_manager.destroy(),this.live_comment_manager=null),this.initialize_failed_message=null,this.comment_list=[]}}}),_i=ci,di=(0,h.Z)(_i,ni,ri,!1,null,"6f8c784f",null),ui=di.exports,mi=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"program-container"},[e("section",{staticClass:"program-broadcaster"},[e("img",{staticClass:"program-broadcaster__icon",attrs:{src:`${t.Utils.api_base_url}/channels/${t.channelsStore.channel_id}/logo`}}),e("div",{staticClass:"program-broadcaster__number"},[t._v("Ch: "+t._s(t.channelsStore.channel.current.channel_number))]),e("div",{staticClass:"program-broadcaster__name"},[t._v(t._s(t.channelsStore.channel.current.channel_name))])]),e("section",{staticClass:"program-info"},[e("h1",{staticClass:"program-info__title",domProps:{innerHTML:t._s(t.ProgramUtils.decorateProgramInfo(t.channelsStore.channel.current.program_present,"title"))}}),e("div",{staticClass:"program-info__time"},[t._v(" "+t._s(t.ProgramUtils.getProgramTime(t.channelsStore.channel.current.program_present))+" ")]),e("div",{staticClass:"program-info__description",domProps:{innerHTML:t._s(t.ProgramUtils.decorateProgramInfo(t.channelsStore.channel.current.program_present,"description"))}}),e("div",{staticClass:"program-info__genre-container"},t._l(t.ProgramUtils.getAttribute(t.channelsStore.channel.current.program_present,"genre",[]),(function(s,i){return e("div",{key:i,staticClass:"program-info__genre"},[t._v(" "+t._s(s.major)+" / "+t._s(s.middle)+" ")])})),0),e("div",{staticClass:"program-info__next"},[e("span",{staticClass:"program-info__next-decorate"},[t._v("NEXT")]),e("Icon",{staticClass:"program-info__next-icon",attrs:{icon:"fluent:fast-forward-20-filled",width:"16px"}})],1),e("span",{staticClass:"program-info__next-title",domProps:{innerHTML:t._s(t.ProgramUtils.decorateProgramInfo(t.channelsStore.channel.current.program_following,"title"))}}),e("div",{staticClass:"program-info__next-time"},[t._v(" "+t._s(t.ProgramUtils.getProgramTime(t.channelsStore.channel.current.program_following))+" ")]),e("div",{staticClass:"program-info__status"},[e("div",{staticClass:"program-info__status-force",class:`program-info__status-force--${t.ChannelUtils.getChannelForceType(t.channelsStore.channel.current.channel_force)}`},[e("Icon",{attrs:{icon:"fa-solid:fire-alt",height:"14px"}}),e("span",{staticClass:"ml-2"},[t._v("勢い:")]),e("span",{staticClass:"ml-2"},[t._v(t._s(t.ProgramUtils.getAttribute(t.channelsStore.channel.current,"channel_force","--"))+" コメ/分")])],1),e("div",{staticClass:"program-info__status-viewers ml-5"},[e("Icon",{attrs:{icon:"fa-solid:eye",height:"14px"}}),e("span",{staticClass:"ml-2"},[t._v("視聴数:")]),e("span",{staticClass:"ml-1"},[t._v(t._s(t.channelsStore.channel.current.viewers))])],1)])]),e("section",{staticClass:"program-detail-container"},t._l(t.ProgramUtils.getAttribute(t.channelsStore.channel.current.program_present,"detail",{}),(function(s,i){return e("div",{key:i,staticClass:"program-detail"},[e("h2",{staticClass:"program-detail__heading"},[t._v(t._s(i))]),e("div",{staticClass:"program-detail__text",domProps:{innerHTML:t._s(t.Utils.URLtoLink(s))}})])})),0)])},hi=[],pi=o["default"].extend({name:"Panel-ProgramTab",data(){return{Utils:ht,ChannelUtils:D,ProgramUtils:mt}},computed:Object.assign({},(0,a.Kc)(Es))}),gi=pi,vi=(0,h.Z)(gi,mi,hi,!1,null,"12609bdf",null),fi=vi.exports,wi=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"twitter-container"},[e(le.Z,{attrs:{"content-class":"zoom-capture-modal-container","max-width":"980",transition:"slide-y-transition"},model:{value:t.zoom_capture_modal,callback:function(e){t.zoom_capture_modal=e},expression:"zoom_capture_modal"}},[e("div",{staticClass:"zoom-capture-modal"},[e("img",{staticClass:"zoom-capture-modal__image",attrs:{src:t.zoom_capture?t.zoom_capture.image_url:""}}),e("a",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"zoom-capture-modal__download",attrs:{href:t.zoom_capture?t.zoom_capture.image_url:"",download:t.zoom_capture?t.zoom_capture.filename:""}},[e("Icon",{attrs:{icon:"fa6-solid:download",width:"45px"}})],1)])]),e("div",{staticClass:"tab-container"},[e("div",{staticClass:"tab-content tab-content--search",class:{"tab-content--active":"Search"===t.twitter_active_tab}},[e("div",{staticClass:"search px-4"},[t._v(" リアルタイム検索機能は鋭意開発中です。 ")])]),e("div",{staticClass:"tab-content tab-content--timeline",class:{"tab-content--active":"Timeline"===t.twitter_active_tab}},[e("div",{staticClass:"search px-4"},[t._v(" タイムライン機能は鋭意開発中です。 ")])]),e("div",{staticClass:"tab-content tab-content--capture",class:{"tab-content--active":"Capture"===t.twitter_active_tab}},[e("div",{staticClass:"captures"},t._l(t.captures,(function(s){return e("div",{key:s.image_url,staticClass:"capture",class:{"capture--selected":s.selected,"capture--focused":s.focused,"capture--disabled":!s.selected&&t.tweet_captures.length>=4},on:{click:function(e){return t.clickCapture(s)}}},[e("img",{staticClass:"capture__image",attrs:{src:s.image_url}}),e("div",{staticClass:"capture__disabled-cover"}),e("div",{staticClass:"capture__selected-number"},[t._v(t._s(t.tweet_captures.findIndex((t=>t===s.blob))+1))]),e("Icon",{staticClass:"capture__selected-checkmark",attrs:{icon:"fluent:checkmark-circle-16-filled"}}),e("div",{staticClass:"capture__selected-border"}),e("div",{staticClass:"capture__focused-border"}),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"capture__zoom",on:{click:function(e){e.preventDefault(),e.stopPropagation(),t.zoom_capture_modal=!0,t.zoom_capture=s},mousedown:function(t){t.preventDefault(),t.stopPropagation()}}},[e("Icon",{attrs:{icon:"fluent:zoom-in-16-regular",width:"32px"}})],1)],1)})),0),e("div",{directives:[{name:"show",rawName:"v-show",value:0===t.captures.length,expression:"captures.length === 0"}],staticClass:"capture-announce"},[e("div",{staticClass:"capture-announce__heading"},[t._v("まだキャプチャがありません。")]),t._m(0)])])]),e("div",{staticClass:"tab-button-container"},[e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"tab-button",class:{"tab-button--active":"Search"===t.twitter_active_tab},on:{click:function(e){t.twitter_active_tab="Search"}}},[e("Icon",{attrs:{icon:"fluent:search-16-filled",height:"18px"}}),e("span",{staticClass:"tab-button__text"},[t._v("ツイート検索")])],1),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"tab-button",class:{"tab-button--active":"Timeline"===t.twitter_active_tab},on:{click:function(e){t.twitter_active_tab="Timeline"}}},[e("Icon",{attrs:{icon:"fluent:home-16-regular",height:"18px"}}),e("span",{staticClass:"tab-button__text"},[t._v("タイムライン")])],1),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"tab-button",class:{"tab-button--active":"Capture"===t.twitter_active_tab},on:{click:function(e){t.twitter_active_tab="Capture"}}},[e("Icon",{attrs:{icon:"fluent:image-copy-20-regular",height:"18px"}}),e("span",{staticClass:"tab-button__text"},[t._v("キャプチャ")])],1)]),e("div",{staticClass:"tweet-form",class:{"tweet-form--focused":t.is_tweet_hashtag_form_focused||t.is_tweet_text_form_focused,"tweet-form--virtual-keyboard-display":t.is_virtual_keyboard_display&&(t.Utils.hasActiveElementClass("tweet-form__hashtag-form")||t.Utils.hasActiveElementClass("tweet-form__textarea"))&&(()=>(t.is_hashtag_list_display=!1,!0))()}},[e("div",{staticClass:"tweet-form__hashtag"},[e("input",{directives:[{name:"model",rawName:"v-model",value:t.tweet_hashtag,expression:"tweet_hashtag"}],staticClass:"tweet-form__hashtag-form",attrs:{type:"search",placeholder:"#ハッシュタグ",spellcheck:"false"},domProps:{value:t.tweet_hashtag},on:{input:[function(e){e.target.composing||(t.tweet_hashtag=e.target.value)},function(e){return t.updateTweetLetterCount()}],focus:function(e){t.is_tweet_hashtag_form_focused=!0},blur:function(e){t.is_tweet_hashtag_form_focused=!1},change:function(e){t.tweet_hashtag=t.formatHashtag(t.tweet_hashtag),t.updateTweetLetterCount()}}}),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"tweet-form__hashtag-list-button",on:{click:function(e){return t.clickHashtagListButton()}}},[e("Icon",{attrs:{icon:"fluent:clipboard-text-ltr-32-regular",height:"22px"}})],1)]),e("textarea",{directives:[{name:"model",rawName:"v-model",value:t.tweet_text,expression:"tweet_text"}],ref:"tweet_text",staticClass:"tweet-form__textarea",attrs:{placeholder:"ツイート",spellcheck:"false"},domProps:{value:t.tweet_text},on:{input:[function(e){e.target.composing||(t.tweet_text=e.target.value)},function(e){return t.updateTweetLetterCount()}],paste:function(e){return t.pasteClipboardData(e)},focus:function(e){t.is_tweet_text_form_focused=!0},blur:function(e){t.is_tweet_text_form_focused=!1}}}),e("div",{staticClass:"tweet-form__control"},[e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"account-button",class:{"account-button--no-login":!t.is_logged_in_twitter},on:{click:function(e){return t.clickAccountButton()}}},[e("img",{staticClass:"account-button__icon",attrs:{src:t.is_logged_in_twitter?t.selected_twitter_account.icon_url:"/assets/images/account-icon-default.png"}}),e("span",{staticClass:"account-button__screen-name"},[t._v(" "+t._s(t.is_logged_in_twitter?`@${t.selected_twitter_account.screen_name}`:"連携されていません")+" ")]),e("Icon",{staticClass:"account-button__menu",attrs:{icon:"fluent:more-circle-20-regular",width:"22px"}})],1),e("div",{staticClass:"limit-meter"},[e("div",{staticClass:"limit-meter__content",class:{"limit-meter__content--yellow":t.tweet_letter_count<=20,"limit-meter__content--red":t.tweet_letter_count<=0}},[e("Icon",{staticStyle:{"margin-right":"-2px"},attrs:{icon:"fa-brands:twitter",width:"12px"}}),e("span",[t._v(t._s(t.tweet_letter_count))])],1),e("div",{staticClass:"limit-meter__content"},[e("Icon",{attrs:{icon:"fluent:image-16-filled",width:"14px"}}),e("span",[t._v(t._s(t.tweet_captures.length)+"/4")])],1)]),e("button",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"tweet-button",attrs:{disabled:!t.is_logged_in_twitter||t.tweet_letter_count<0||140===t.tweet_letter_count&&0===t.tweet_captures.length},on:{click:function(e){return t.sendTweet()},touchstart:function(e){return t.sendTweet()}}},[e("Icon",{attrs:{icon:"fa-brands:twitter",height:"16px"}}),e("span",{staticClass:"ml-1"},[t._v("ツイート")])],1)])]),e("div",{staticClass:"hashtag-list",class:{"hashtag-list--display":t.is_hashtag_list_display,"hashtag-list--virtual-keyboard-display":t.is_virtual_keyboard_display&&t.Utils.hasActiveElementClass("hashtag__input")}},[e("div",{staticClass:"hashtag-heading"},[e("div",{staticClass:"hashtag-heading__text"},[e("Icon",{attrs:{icon:"charm:hash",width:"17px"}}),e("span",{staticClass:"ml-1"},[t._v("ハッシュタグリスト")])],1),e("button",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"hashtag-heading__add-button",on:{click:function(e){t.saved_twitter_hashtags.push({id:t.Utils.time(),text:"#ここにハッシュタグを入力",editing:!1})}}},[e("Icon",{attrs:{icon:"fluent:add-12-filled",width:"17px"}}),e("span",{staticClass:"ml-1"},[t._v("追加")])],1)]),e("draggable",{staticClass:"hashtag-container",attrs:{handle:".hashtag__sort-handle"},model:{value:t.saved_twitter_hashtags,callback:function(e){t.saved_twitter_hashtags=e},expression:"saved_twitter_hashtags"}},t._l(t.saved_twitter_hashtags,(function(s){return e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple",value:!s.editing,expression:"!hashtag.editing"}],key:s.id,staticClass:"hashtag",class:{"hashtag--editing":s.editing},on:{click:function(e){return t.clickHashtag(s)}}},[e("input",{directives:[{name:"model",rawName:"v-model",value:s.text,expression:"hashtag.text"}],staticClass:"hashtag__input",attrs:{type:"search",spellcheck:"false",disabled:!s.editing},domProps:{value:s.text},on:{click:function(t){t.stopPropagation()},input:function(e){e.target.composing||t.$set(s,"text",e.target.value)}}}),e("button",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"hashtag__edit-button",on:{click:function(e){e.preventDefault(),e.stopPropagation(),s.editing=!s.editing,s.text=t.formatHashtag(s.text,!0),t.updateTweetLetterCount()}}},[e("Icon",{attrs:{icon:s.editing?"fluent:checkmark-16-filled":"fluent:edit-16-filled",width:"17px"}})],1),e("button",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"hashtag__delete-button",on:{click:function(e){e.preventDefault(),e.stopPropagation(),t.saved_twitter_hashtags.splice(t.saved_twitter_hashtags.indexOf(s),1)}}},[e("Icon",{attrs:{icon:"fluent:delete-16-filled",width:"17px"}})],1),e("div",{staticClass:"hashtag__sort-handle"},[e("Icon",{attrs:{icon:"material-symbols:drag-handle-rounded",width:"17px"}})],1)])})),0)],1),e("div",{staticClass:"twitter-account-list",class:{"twitter-account-list--display":t.is_twitter_account_list_display}},t._l(t.userStore.user?t.userStore.user.twitter_accounts:[],(function(s){return e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],key:s.id,staticClass:"twitter-account",on:{click:function(e){return t.updateSelectedTwitterAccount(s)}}},[e("img",{staticClass:"twitter-account__icon",attrs:{src:s.icon_url}}),e("div",{staticClass:"twitter-account__info"},[e("div",{staticClass:"twitter-account__name"},[t._v(t._s(s.name))]),e("div",{staticClass:"twitter-account__screen-name"},[t._v("@"+t._s(s.screen_name))])]),e("Icon",{directives:[{name:"show",rawName:"v-show",value:s.id===t.settingsStore.settings.selected_twitter_account_id,expression:"twitter_account.id === settingsStore.settings.selected_twitter_account_id"}],staticClass:"twitter-account__check",attrs:{icon:"fluent:checkmark-16-filled",width:"24px"}})],1)})),0)],1)},yi=[function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"capture-announce__text"},[e("p",{staticClass:"mt-0 mb-0"},[t._v("プレイヤーのキャプチャボタンやショートカットキーでキャプチャを撮ると、ここに表示されます。")]),e("p",{staticClass:"mt-2 mb-0"},[t._v("表示されたキャプチャを選択してからツイートすると、キャプチャを付けてツイートできます。")])])}],bi=s(9980),Ci=s.n(bi),ki=o["default"].extend({name:"Panel-TwitterTab",components:{draggable:Ci()},props:{player:{type:null,required:!0},is_virtual_keyboard_display:{type:Boolean,required:!0}},data(){return{Utils:ht,is_logged_in_twitter:!1,selected_twitter_account:null,is_twitter_account_list_display:!1,saved_twitter_hashtags:st().settings.saved_twitter_hashtags.map(((t,e)=>({id:ht.time()+e,text:t,editing:!1}))),is_hashtag_list_display:!1,twitter_active_tab:st().settings.twitter_active_tab,zoom_capture_modal:!1,zoom_capture:null,captures:[],captures_element:null,is_tweet_hashtag_form_focused:!1,is_tweet_text_form_focused:!1,tweet_hashtag:"",tweet_text:"",tweet_captures:[],tweet_letter_count:140,is_tweet_sending:!1}},computed:Object.assign({},(0,a.Kc)(Es,st,R)),async created(){if(await this.userStore.fetchUser(),!0===this.userStore.is_logged_in&&this.userStore.user.twitter_accounts.length>0){this.is_logged_in_twitter=!0,null!==this.settingsStore.settings.selected_twitter_account_id&&this.userStore.user.twitter_accounts.some((t=>t.id===this.settingsStore.settings.selected_twitter_account_id))||(this.settingsStore.settings.selected_twitter_account_id=this.userStore.user.twitter_accounts[0].id);const t=this.userStore.user.twitter_accounts.findIndex((t=>t.id===this.settingsStore.settings.selected_twitter_account_id));this.selected_twitter_account=this.userStore.user.twitter_accounts[t]}this.tweet_hashtag=this.formatHashtag(this.tweet_hashtag),this.updateTweetLetterCount()},beforeDestroy(){for(const t of this.captures)URL.revokeObjectURL(t.image_url)},watch:{saved_twitter_hashtags:{deep:!0,handler(){this.settingsStore.settings.saved_twitter_hashtags=this.saved_twitter_hashtags.map((t=>t.text))}}},methods:{updateTweetLetterCount(){this.tweet_letter_count=140-[...this.tweet_hashtag].length-[...this.tweet_text].length},pasteClipboardData(t){for(const e of t.clipboardData.items)if(e.type.startsWith("image/")){const t=e.getAsFile();this.addCaptureList(t,t.name)}},clickHashtagListButton(){this.is_hashtag_list_display=!this.is_hashtag_list_display;for(const t of this.saved_twitter_hashtags)t.editing=!1},clickHashtag(t){this.tweet_hashtag=t.text,this.tweet_hashtag=this.formatHashtag(this.tweet_hashtag),this.updateTweetLetterCount(),window.setTimeout((()=>this.is_hashtag_list_display=!1),150)},clickAccountButton(){if(!this.is_logged_in_twitter)return document.fullscreenElement&&document.exitFullscreen(),void this.$router.push({path:"/settings/twitter"});this.is_twitter_account_list_display=!this.is_twitter_account_list_display,!0===this.is_twitter_account_list_display&&(this.is_hashtag_list_display=!1)},updateSelectedTwitterAccount(t){this.settingsStore.settings.selected_twitter_account_id=t.id,this.selected_twitter_account=t,window.setTimeout((()=>this.is_twitter_account_list_display=!1),150)},clickCapture(t){if(this.tweet_captures.length<4&&!1===t.selected)t.selected=!0,this.tweet_captures.push(t.blob);else{const e=this.tweet_captures.findIndex((e=>e===t.blob));e>-1&&this.tweet_captures.splice(e,1),t.selected=!1}},async addCaptureList(t,e){null===this.captures_element&&(this.captures_element=this.$el.querySelector(".tab-content--capture")),this.captures.length>50&&(URL.revokeObjectURL(this.captures[0].image_url),this.captures.shift());const s=URL.createObjectURL(t);this.captures.push({blob:t,filename:e,image_url:s,selected:!1,focused:!1}),this.$nextTick((()=>{this.captures_element.scrollTo({top:this.captures_element.scrollHeight,behavior:"smooth"})}))},async drawProgramTitleOnCapture(t){var e,s;const i=await createImageBitmap(t),a="OffscreenCanvas"in window?new OffscreenCanvas(i.width,i.height):document.createElement("canvas"),n=a.getContext("2d",{alpha:!1,desynchronized:!0,willReadFrequently:!1});n.drawImage(i,0,0),i.close(),n.font='bold 22px "YakuHanJPs", "Open Sans", "Hiragino Sans", "Noto Sans JP", sans-serif',n.fillStyle="rgba(255, 255, 255, 70%)",n.shadowColor="rgba(0, 0, 0, 100%)",n.shadowBlur=4,n.shadowOffsetX=0,n.shadowOffsetY=0;const r=null!==(s=null===(e=this.channelsStore.channel.current.program_present)||void 0===e?void 0:e.title)&&void 0!==s?s:"放送休止";switch(this.settingsStore.settings.tweet_capture_watermark_position){case"TopLeft":n.textAlign="left",n.textBaseline="top",n.fillText(r,16,12);break;case"TopRight":n.textAlign="right",n.textBaseline="top",n.fillText(r,a.width-16,12);break;case"BottomLeft":n.textAlign="left",n.textBaseline="bottom",n.fillText(r,16,a.height-12);break;case"BottomRight":n.textAlign="right",n.textBaseline="bottom",n.fillText(r,a.width-16,a.height-12);break}return a instanceof OffscreenCanvas?await a.convertToBlob({type:"image/jpeg",quality:1}):new Promise((t=>a.toBlob((e=>t(e)),"image/jpeg",1)))},getChannelHashtag(t){return t.startsWith("NHK総合")?"#nhk":t.startsWith("NHKEテレ")?"#etv":t.startsWith("日テレ")?"#ntv":t.startsWith("読売テレビ")?"#ytv":t.startsWith("中京テレビ")?"#chukyotv":t.startsWith("テレビ朝日")?"#tvasahi":t.startsWith("ABCテレビ")?"#abc":t.startsWith("メ~テレ")?"#nagoyatv":t.startsWith("TBS")&&!t.includes("TBSチャンネル")?"#tbs":t.startsWith("MBS")?"#mbs":t.startsWith("CBC")?"#cbc":t.startsWith("テレビ東京")?"#tvtokyo":t.startsWith("テレビ大阪")?"#tvo":t.startsWith("テレビ愛知")?"#tva":t.startsWith("フジテレビ")?"#fujitv":t.startsWith("関西テレビ")?"#kantele":t.startsWith("東海テレビ")?"#tokaitv":t.startsWith("TOKYO MX")?"#tokyomx":t.startsWith("tvk")?"#tvk":t.startsWith("チバテレ")?"#chibatv":t.startsWith("テレ玉")?"#teletama":t.startsWith("サンテレビ")?"#suntv":t.startsWith("KBS京都")?"#kbs":t.startsWith("NHKBS1")?"#nhkbs1":t.startsWith("NHKBSプレミアム")?"#nhkbsp":t.startsWith("BS日テレ")?"#bsntv":t.startsWith("BS朝日")?"#bsasahi":t.startsWith("BS-TBS")?"#bstbs":t.startsWith("BSテレ東")?"#bstvtokyo":t.startsWith("BSフジ")?"#bsfuji":t.startsWith("BS11イレブン")?"#bs11":t.startsWith("BS12トゥエルビ")?"#bs12":t.startsWith("AT-X")?"#at_x":null},formatHashtag(t,e=!1){const s=t.trim().replaceAll("♯","#").replaceAll("#","#").replace(/#{2,}/g,"#").replaceAll(" "," ").replaceAll(/ +/g," ").split(" ").filter((t=>""!==t));for(let i in s)s[i].startsWith("#")||(s[i]=`#${s[i]}`);if(!0===this.settingsStore.settings.auto_add_watching_channel_hashtag&&!1===e){const t=this.getChannelHashtag(this.channelsStore.channel.current.channel_name);null!==t&&!1===s.includes(t)&&s.push(t)}return s.join(" ")},async sendTweet(){if(!0===this.is_tweet_sending)return;this.is_tweet_sending=!0,this.tweet_hashtag=this.formatHashtag(this.tweet_hashtag),this.updateTweetLetterCount();const t=this.tweet_hashtag;let e=this.tweet_text;if(""!==t)switch(this.settingsStore.settings.tweet_hashtag_position){case"Prepend":e=`${t} ${this.tweet_text}`;break;case"Append":e=`${this.tweet_text} ${t}`;break;case"PrependWithLineBreak":e=`${t}\n${this.tweet_text}`;break;case"AppendWithLineBreak":e=`${this.tweet_text}\n${t}`;break}const s=[];for(let i of this.tweet_captures)"None"!==this.settingsStore.settings.tweet_capture_watermark_position&&(i=await this.drawProgramTitleOnCapture(i)),s.push(i);Ts.sendTweet(this.selected_twitter_account.screen_name,e,s).then((t=>{this.player.notice(t.message)}));for(const i of this.captures)i.selected=!1,i.focused=!1;this.tweet_captures=[],this.tweet_text="",this.is_tweet_sending=!1,!0===this.settingsStore.settings.fold_panel_after_sending_tweet&&(this.$emit("panel_folding_requested"),this.$refs.tweet_text.blur())}}}),xi=ki,Si=(0,h.Z)(xi,wi,yi,!1,null,"62dbc198",null),Oi=Si.exports,Ti=(s(3767),s(8585),s(8696),s(9908)),ji=s(8488);class Ii{static async uploadCapture(t,e){const s=new FormData;s.append("image",t,e);const i=await G.post("/captures",s,{headers:{"Content-Type":"multipart/form-data"}});if("is_error"in i)switch(i.error.message){case"Permission denied to save the file":K.error("キャプチャのアップロードに失敗しました。保存先フォルダに書き込み権限がありません。");break;case"No space left on the device":K.error("キャプチャのアップロードに失敗しました。保存先フォルダに空き容量がありません。");break;case"Unexpected error occurred while saving the file":K.error("キャプチャのアップロードに失敗しました。保存中に予期しないエラーが発生しました。");break;default:G.showGenericError(i,"キャプチャのアップロードに失敗しました。");break}else;}}var Pi=Ii;let Zi=null,zi=null;class $i{constructor(t,e){this.settings_store=st(),this.player=t,this.player_container=this.player.container,this.captured_callback=e,this.player_container.querySelector(".dplayer-icons.dplayer-icons-right").insertAdjacentHTML("afterbegin",'\n
\n \n \n \n
\n '),this.player_container.querySelector(".dplayer-icons.dplayer-icons-right").insertAdjacentHTML("afterbegin",'\n
\n \n \n \n
\n '),this.comment_capture_button=this.player_container.querySelector(".dplayer-comment-capture-icon"),this.capture_button=this.player_container.querySelector(".dplayer-capture-icon"),this.canvas="OffscreenCanvas"in window?new OffscreenCanvas(0,0):document.createElement("canvas"),this.canvas_context=this.canvas.getContext("2d",{alpha:!1,desynchronized:!0,willReadFrequently:!1}),this.canvas.width=0,this.canvas.height=0,t.on("loadedmetadata",(async()=>{this.canvas.width=t.video.videoWidth,this.canvas.height=t.video.videoHeight;while(0===this.canvas.width&&0===this.canvas.height)await ht.sleep(.1),this.canvas.width=t.video.videoWidth,this.canvas.height=t.video.videoHeight})),(async()=>{const t="https://cdn.jsdelivr.net/npm/noto-sans-japanese@1.0.0/fonts/NotoSansJP-Bold.woff2",e="https://cdn.jsdelivr.net/npm/open-sans-all@0.1.3/fonts/open-sans-700.woff2",s="data:font/woff2;base64,";if(null===Zi){const e=(await G.get(t,{responseType:"arraybuffer"})).data;Zi=s+B.lW.from(e).toString("base64")}if(null===zi){const t=(await G.get(e,{responseType:"arraybuffer"})).data;zi=s+B.lW.from(t).toString("base64")}})()}async captureAndSave(t){var e,s,i,a,n,r,o,l,c,_,d,u;const m=ht.time(),h=Es(),p=h.is_showing_live?h.channel.current:null;if(null!==p&&!0===p.is_radiochannel)return void this.player.notice("ラジオチャンネルはキャプチャできません。");if(0===this.canvas.width&&0===this.canvas.height)return void this.player.notice("読み込み中はキャプチャできません。");if(!0===t&&!1===this.player.danmaku.showing)return void this.player.notice("コメントを付けてキャプチャするには、コメント表示をオンにしてください。");this.addHighlight(t);const g=`Capture_${rt()().format("YYYYMMDD-HHmmss")}`,v=`${g}.jpg`,f=`${g}_caption.jpg`,w=this.player.plugins.aribb24Caption.getRawCanvas(),y=this.player.plugins.aribb24Superimpose.getRawCanvas(),b=!0===this.player.plugins.aribb24Caption.isShowing&&this.player.plugins.aribb24Caption.isPresent(),C=!0===this.player.plugins.aribb24Superimpose.isShowing&&this.player.plugins.aribb24Superimpose.isPresent(),k=b?this.player.plugins.aribb24Caption.getTextContent():null;let x;null!==p&&(x={network_id:p.network_id,service_id:p.service_id,event_id:null!==(s=null===(e=p.program_present)||void 0===e?void 0:e.event_id)&&void 0!==s?s:-1,title:null!==(a=null===(i=p.program_present)||void 0===i?void 0:i.title)&&void 0!==a?a:"放送休止",description:null!==(r=null===(n=p.program_present)||void 0===n?void 0:n.description)&&void 0!==r?r:"",start_time:null!==(l=null===(o=p.program_present)||void 0===o?void 0:o.start_time)&&void 0!==l?l:"2000-01-01T00:00:00+09:00",end_time:null!==(_=null===(c=p.program_present)||void 0===c?void 0:c.end_time)&&void 0!==_?_:"2000-01-01T00:00:00+09:00",duration:null!==(u=null===(d=p.program_present)||void 0===d?void 0:d.duration)&&void 0!==u?u:0,caption_text:k,is_caption_composited:!1,is_comment_composited:!1});const S=async(t,e,s)=>{const i=ht.time();let a;try{a=await this.exportToBlob(t)}catch(n){return console.log(n),this.player.notice("キャプチャの保存に失敗しました…"),!1}return console.log("[CaptureHandler] Export to Blob:",ht.mathFloor(ht.time()-i,3),"sec"),a=await this.setEXIFDataToCapture(a,s),["Browser","Both"].includes(this.settings_store.settings.capture_save_mode)&&ht.downloadBlobData(a,e),["UploadServer","Both"].includes(this.settings_store.settings.capture_save_mode)&&Pi.uploadCapture(a,e),a};let O=null,T=null;const j=await createImageBitmap(this.player.video);if(!1!==t||!1!==C||!1!==b&&"VideoOnly"!==this.settings_store.settings.capture_caption_mode){const e=[];this.canvas_context.drawImage(j,0,0,this.canvas.width,this.canvas.height),!0===C&&this.canvas_context.drawImage(y,0,0,this.canvas.width,this.canvas.height);let s=null;!0===t&&(s=await this.createCommentsImage(),await this.drawComments(s)),(["VideoOnly","Both"].includes(this.settings_store.settings.capture_caption_mode)||!1===b)&&e.push((async()=>{const e="CompositingCaption"===this.settings_store.settings.capture_caption_mode?f:v,s=await S(this.canvas,e,Object.assign(Object.assign({},x),{is_caption_composited:!1,is_comment_composited:t}));O=!1!==s&&{blob:s,filename:e},!1!==O&&this.captured_callback(O.blob,O.filename)})()),["CompositingCaption","Both"].includes(this.settings_store.settings.capture_caption_mode)&&!0===b&&e.push((async()=>{!0===t&&(this.canvas_context.drawImage(j,0,0,this.canvas.width,this.canvas.height),!0===C&&this.canvas_context.drawImage(y,0,0,this.canvas.width,this.canvas.height)),j.close(),this.canvas_context.drawImage(w,0,0,this.canvas.width,this.canvas.height),!0===t&&await this.drawComments(s);const e=await S(this.canvas,f,Object.assign(Object.assign({},x),{is_caption_composited:!0,is_comment_composited:t}));if(T=!1!==e&&{blob:e,filename:f},!1!==T){if("Both"===this.settings_store.settings.capture_caption_mode)while(null===O)await ht.sleep(.01);this.captured_callback(T.blob,T.filename)}})()),await Promise.all(e)}else{const t="OffscreenCanvas"in window?new OffscreenCanvas(j.width,j.height):document.createElement("canvas");t.width=j.width,t.height=j.height;const e=t.getContext("bitmaprenderer",{alpha:!1});e.transferFromImageBitmap(j),j.close();const s="CompositingCaption"===this.settings_store.settings.capture_caption_mode?f:v,i=await S(t,s,Object.assign(Object.assign({},x),{is_caption_composited:!1,is_comment_composited:!1}));O=!1!==i&&{blob:i,filename:s},!1!==O&&this.captured_callback(O.blob,O.filename)}console.log("[CaptureHandler] Total:",ht.mathFloor(ht.time()-m,3),"sec"),this.removeHighlight(t);for(const P of[O,T])if(this.settings_store.settings.capture_copy_to_clipboard&&null!==P&&!1!==P)try{await(0,Ti.FH)(await(0,Ti.BD)(P.blob))}catch(I){this.player.notice("クリップボードへのキャプチャのコピーに失敗しました…"),console.error(I)}}addHighlight(t=!1){t?this.comment_capture_button.classList.add("dplayer-capturing"):this.capture_button.classList.add("dplayer-capturing")}removeHighlight(t=!1){t?this.comment_capture_button.classList.remove("dplayer-capturing"):this.capture_button.classList.remove("dplayer-capturing")}async commentsHTMLtoSVGImage(t,e,s){const i=`\n \n \n
\n \n ${t}\n
\n
\n
\n `.trim(),a=new Image;return a.src=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(i)}`,await a.decode(),a}async createCommentsImage(){let t=this.player.template.danmaku.outerHTML;for(const e of this.player_container.querySelectorAll(".dplayer-danmaku-move")){const s=e.getBoundingClientRect().left-this.player.video.getBoundingClientRect().left;t=t.replace(/transform: translateX\(.*?\);/,`left: ${s}px;`).replaceAll("border: 2px solid #E64F97;","")}return await this.commentsHTMLtoSVGImage(t,this.player.template.danmaku.offsetWidth,this.player.template.danmaku.offsetHeight)}async drawComments(t){const e=this.canvas.width/this.player.template.danmaku.offsetWidth,s=this.player.template.danmaku.offsetHeight*e;this.canvas_context.drawImage(t,0,0,this.canvas.width,s)}async exportToBlob(t){return"OffscreenCanvas"in window&&t instanceof OffscreenCanvas?await t.convertToBlob({type:"image/jpeg",quality:.99}):t instanceof HTMLCanvasElement?new Promise(((e,s)=>{t.toBlob((t=>{null!==t?e(t):s(new Error("Failed to convert canvas to blob"))}),"image/jpeg",.99)})):void 0}async setEXIFDataToCapture(t,e){const s=rt()().diff(rt()(e.start_time),"second",!0),i={captured_at:rt()().format("YYYY-MM-DDTHH:mm:ss+09:00"),captured_playback_position:s,network_id:e.network_id,service_id:e.service_id,event_id:e.event_id,title:e.title,description:e.description,start_time:e.start_time,end_time:e.end_time,duration:e.duration,caption_text:e.caption_text,is_caption_composited:e.is_caption_composited,is_comment_composited:e.is_comment_composited},a=rt()().format("YYYY:MM:DD HH:mm:ss"),n={"0th":{[ji.TagValues.ImageIFD.XResolution]:[72,1],[ji.TagValues.ImageIFD.YResolution]:[72,1],[ji.TagValues.ImageIFD.ResolutionUnit]:2,[ji.TagValues.ImageIFD.YCbCrPositioning]:1,[ji.TagValues.ImageIFD.DateTime]:a,[ji.TagValues.ImageIFD.Software]:`KonomiTV version ${ht.version}`,[ji.TagValues.ImageIFD.XPComment]:[...B.lW.from(JSON.stringify(i),"ucs2")]},Exif:{[ji.TagValues.ExifIFD.ExifVersion]:"0230",[ji.TagValues.ExifIFD.ComponentsConfiguration]:"\0",[ji.TagValues.ExifIFD.FlashpixVersion]:"0100",[ji.TagValues.ExifIFD.ColorSpace]:1,[ji.TagValues.ExifIFD.DateTimeOriginal]:a,[ji.TagValues.ExifIFD.DateTimeDigitized]:a}},r=ji.dump(n),o=await new Promise(((e,s)=>{const i=new FileReader;i.onload=()=>e(i.result),i.onerror=s,i.readAsBinaryString(t)})),l=ji.insert(r,o),c=new Uint8Array(l.length);for(let _=0;_{const t=st();switch(t.settings.panel_display_state){case"AlwaysDisplay":return!0;case"AlwaysFold":return!1;case"RestorePreviousState":return t.settings.showed_panel_last_time}})(),is_fullscreen:!1,is_ime_composing:!1,is_virtual_keyboard_display:!1,is_comment_send_just_did:!1,interval_ids:[],control_interval_id:0,is_zapping:!1,is_zapping_continuously:!1,player:null,player_can_be_destroyed:!1,is_mpegts_supported:!0===Js().isSupported(),romsounds_context:null,romsounds_buffers:[],eventsource:null,fullscreen_handler:null,capture_handler:null,shortcut_key_handler:null,shortcut_key_pressed_at:ht.time(),shortcut_key_modal:!1,shortcut_key_list:{left_column:[{name:"全般",icon:"fluent:home-20-filled",icon_height:"22px",shortcuts:[{name:"数字キー/テンキーに対応するリモコン番号 (1~12) の地デジチャンネルに切り替える",keys:[{name:"1~9, 0, -(=), ^(~)",icon:!1}]},{name:"数字キー/テンキーに対応するリモコン番号 (1~12) の BS チャンネルに切り替える",keys:[{name:"Shift",icon:!1},{name:"1~9, 0, -(=), ^(~)",icon:!1}]},{name:"前のチャンネルに切り替える",keys:[{name:"fluent:arrow-up-12-filled",icon:!0}]},{name:"次のチャンネルに切り替える",keys:[{name:"fluent:arrow-down-12-filled",icon:!0}]},{name:"キーボードショートカットの一覧を表示する",keys:[{name:"/(?)",icon:!1}]}]},{name:"プレイヤー",icon:"fluent:play-20-filled",icon_height:"20px",shortcuts:[{name:"再生 / 一時停止の切り替え",keys:[{name:"Space",icon:!1}]},{name:"再生 / 一時停止の切り替え (キャプチャタブ表示時)",keys:[{name:"Shift",icon:!1},{name:"Space",icon:!1}]},{name:"プレイヤーの音量を上げる",keys:[{name:ht.CtrlOrCmd(),icon:!1},{name:"fluent:arrow-up-12-filled",icon:!0}]},{name:"プレイヤーの音量を下げる",keys:[{name:ht.CtrlOrCmd(),icon:!1},{name:"fluent:arrow-down-12-filled",icon:!0}]},{name:"停止して0.5秒早戻し",keys:[{name:ht.CtrlOrCmd(),icon:!1},{name:"fluent:arrow-left-12-filled",icon:!0}]},{name:"停止して0.5秒早送り",keys:[{name:ht.CtrlOrCmd(),icon:!1},{name:"fluent:arrow-right-12-filled",icon:!0}]},{name:"フルスクリーンの切り替え",keys:[{name:"F",icon:!1}]},{name:"ライブストリームの同期",keys:[{name:"W",icon:!1}]},{name:"Picture-in-Picture の表示切り替え",keys:[{name:"E",icon:!1}]},{name:"字幕の表示切り替え",keys:[{name:"S",icon:!1}]},{name:"コメントの表示切り替え",keys:[{name:"D",icon:!1}]},{name:"映像をキャプチャする",keys:[{name:"C",icon:!1}]},{name:"映像をコメントを付けてキャプチャする",keys:[{name:"V",icon:!1}]},{name:"コメント入力フォームにフォーカスする",keys:[{name:"M",icon:!1}]},{name:"コメント入力フォームを閉じる",keys:[{name:ht.CtrlOrCmd(),icon:!1},{name:"M",icon:!1}]}]}],right_column:[{name:"パネル",icon:"fluent:panel-right-20-filled",icon_height:"24px",shortcuts:[{name:"パネルの表示切り替え",keys:[{name:"P",icon:!1}]},{name:"番組情報タブを表示する",keys:[{name:"K",icon:!1}]},{name:"チャンネルタブを表示する",keys:[{name:"L",icon:!1}]},{name:"コメントタブを表示する",keys:[{name:";(+)",icon:!1}]},{name:"Twitter タブを表示する",keys:[{name:":(*)",icon:!1}]}]},{name:"Twitter",icon:"fa-brands:twitter",icon_height:"22px",shortcuts:[{name:"ツイート検索タブを表示する",keys:[{name:"[ (「)",icon:!1}]},{name:"タイムラインタブを表示する",keys:[{name:"] (」)",icon:!1}]},{name:"キャプチャタブを表示する",keys:[{name:"\(¥)",icon:!1}]},{name:"ツイート入力フォームにフォーカスを当てる/フォーカスを外す",keys:[{name:"Tab",icon:!1}]},{name:"キャプチャにフォーカスする",keys:[{name:"キャプチャタブを表示",icon:!1},{name:"fluent:arrow-up-12-filled;fluent:arrow-down-12-filled;fluent:arrow-left-12-filled;fluent:arrow-right-12-filled",icon:!0}]},{name:"キャプチャを拡大表示する/
キャプチャの拡大表示を閉じる",keys:[{name:"キャプチャにフォーカス",icon:!1},{name:"Enter",icon:!1}]},{name:"キャプチャを選択する/
キャプチャの選択を解除する",keys:[{name:"キャプチャにフォーカス",icon:!1},{name:"Space",icon:!1}]},{name:"クリップボード内の画像を
キャプチャとして取り込む",keys:[{name:"ツイート入力
フォームにフォーカス",icon:!1},{name:ht.CtrlOrCmd(),icon:!1},{name:"V",icon:!1}]},{name:"ツイートを送信する",keys:[{name:"Twitter タブを表示",icon:!1},{name:ht.CtrlOrCmd(),icon:!1},{name:"Enter",icon:!1}]}]}]}}},computed:Object.assign({},(0,a.Kc)(Es,st)),async created(){!0===this.settingsStore.settings.tv_channel_selection_requires_alt_key&&(this.shortcut_key_list.left_column[0].shortcuts[0].keys.unshift({name:ht.AltOrOption(),icon:!1}),this.shortcut_key_list.left_column[0].shortcuts[1].keys.unshift({name:ht.AltOrOption(),icon:!1})),this.channelsStore.channel_id=this.$route.params.channel_id,"virtualKeyboard"in navigator&&(navigator.virtualKeyboard.overlaysContent=!0,navigator.virtualKeyboard.ongeometrychange=t=>{0===t.target.boundingRect.width&&0===t.target.boundingRect.height?this.is_virtual_keyboard_display=!1:this.is_virtual_keyboard_display=!0}),this.init(),this.romsounds_context=new AudioContext;for(let t=1;t<=14;t++){const e=`/assets/romsounds/${t.toString().padStart(2,"0")}.wav`,s=await G.get(e,{baseURL:"",responseType:"arraybuffer"});this.romsounds_buffers.push(await this.romsounds_context.decodeAudioData(s.data))}},beforeDestroy(){"virtualKeyboard"in navigator&&(navigator.virtualKeyboard.overlaysContent=!1),"gr999"!==this.channelsStore.channel.current.channel_id&&this.destroy(!0),null!==this.romsounds_context&&this.romsounds_context.close(),this.channelsStore.channel_id="gr000"},beforeRouteUpdate(t,e,s){const i=this.destroy(!1,this.is_zapping_continuously);this.is_zapping_continuously=!0,this.channelsStore.channel_id=t.params.channel_id,!0===this.settingsStore.settings.reset_hashtag_when_program_switches&&(this.$refs.Twitter.tweet_hashtag=""),(async()=>{!0===this.is_zapping?(this.is_zapping=!1,this.interval_ids.push(window.setTimeout((()=>{this.is_zapping_continuously=!1,i.then((()=>this.init()))}),500))):(this.is_zapping_continuously=!1,i.then((()=>this.init())))})(),s()},watch:{"channelsStore.channel":{immediate:!0,handler(t,e){var s;if(void 0===e)return;const i=this.$refs.Twitter;if(t.current.channel_id!==e.current.channel_id){const t=null!==(s=i.getChannelHashtag(e.current.channel_name))&&void 0!==s?s:"";i.tweet_hashtag=i.formatHashtag(i.tweet_hashtag.replaceAll(t,"")),i.updateTweetLetterCount()}(t.current.id!==e.current.id||null!==t.current.program_present&&null===e.current.program_present||null===t.current.program_present&&null!==e.current.program_present||null!==t.current.program_present&&null!==e.current.program_present&&t.current.program_present.id!==e.current.program_present.id)&&!0===this.settingsStore.settings.reset_hashtag_when_program_switches&&(i.tweet_hashtag=i.formatHashtag(""),i.updateTweetLetterCount())}},is_panel_display(){this.settingsStore.settings.showed_panel_last_time=this.is_panel_display}},methods:{async init(){this.background_url=at.generatePlayerBackgroundURL(),this.controlDisplayTimer(),this.interval_ids.push(window.setInterval((()=>this.time=rt()().format("YYYY/MM/DD HH:mm:ss")),1e3));const t=60-(new Date).getSeconds();this.interval_ids.push(window.setTimeout((()=>{this.channelsStore.update(!0),this.update(),this.interval_ids.push(window.setInterval((()=>{this.channelsStore.update(!0),this.update()}),3e4))}),1e3*t)),await this.channelsStore.update(),this.update()},async update(){var t,e;if(void 0!==this.$route.params.channel_id)if("gr999"!==this.channelsStore.channel.current.channel_id){if(null!==this.player&&!0!==this.player_can_be_destroyed||(this.initPlayer(),this.initEventHandler(),this.initCaptureHandler(),document.removeEventListener("keydown",this.shortcut_key_handler),this.initShortcutKeyHandler()),null!==this.player){if(null===this.channelsStore.channel.current.program_present||"1/0+1/0モード(デュアルモノ)"!==this.channelsStore.channel.current.program_present.primary_audio_type&&null===this.channelsStore.channel.current.program_present.secondary_audio_type){if(this.player.template.audioItem[1].classList.add("dplayer-setting-audio-item--disabled"),void 0!==this.player.plugins.mpegts||void 0!==this.player.plugins.liveLLHLSForKonomiTV){while(null===this.player)await ht.sleep(.1);this.player.template.audioItem[0].classList.add("dplayer-setting-audio-current"),this.player.template.audioItem[1].classList.remove("dplayer-setting-audio-current"),this.player.template.audioValue.textContent=this.player.tran("Primary audio");try{void 0!==this.player.plugins.mpegts&&this.player.plugins.mpegts instanceof Js().MSEPlayer&&this.player.plugins.mpegts.switchPrimaryAudio(),void 0!==this.player.plugins.liveLLHLSForKonomiTV&&this.player.plugins.liveLLHLSForKonomiTV.switchPrimaryAudio()}catch(s){}}}else this.player.template.audioItem[1].classList.remove("dplayer-setting-audio-item--disabled");if("mediaSession"in navigator){const s=[{src:"/assets/images/icons/icon-maskable-192px.png",sizes:"192x192",type:"image/png"},{src:"/assets/images/icons/icon-maskable-512px.png",sizes:"512x512",type:"image/png"}];navigator.mediaSession.metadata=new MediaMetadata({title:null!==(e=null===(t=this.channelsStore.channel.current.program_present)||void 0===t?void 0:t.title)&&void 0!==e?e:"放送休止",artist:this.channelsStore.channel.current.channel_name,artwork:s}),"setPositionState"in navigator.mediaSession&&navigator.mediaSession.setPositionState({duration:0,playbackRate:1}),navigator.mediaSession.setActionHandler("play",null),navigator.mediaSession.setActionHandler("pause",null),navigator.mediaSession.setActionHandler("previoustrack",null),navigator.mediaSession.setActionHandler("nexttrack",null),navigator.mediaSession.setActionHandler("play",(()=>{var t;return null===(t=this.player)||void 0===t?void 0:t.play()})),navigator.mediaSession.setActionHandler("pause",(()=>{var t;return null===(t=this.player)||void 0===t?void 0:t.pause()})),navigator.mediaSession.setActionHandler("previoustrack",(async()=>{var t,e;navigator.mediaSession.metadata=new MediaMetadata({title:null!==(e=null===(t=this.channelsStore.channel.previous.program_present)||void 0===t?void 0:t.title)&&void 0!==e?e:"放送休止",artist:this.channelsStore.channel.previous.channel_name,artwork:s}),await this.$router.push({path:`/tv/watch/${this.channelsStore.channel.previous.channel_id}`})})),navigator.mediaSession.setActionHandler("nexttrack",(async()=>{var t,e;navigator.mediaSession.metadata=new MediaMetadata({title:null!==(e=null===(t=this.channelsStore.channel.next.program_present)||void 0===t?void 0:t.title)&&void 0!==e?e:"放送休止",artist:this.channelsStore.channel.next.channel_name,artwork:s}),await this.$router.push({path:`/tv/watch/${this.channelsStore.channel.next.channel_id}`})}))}}}else this.$router.push({path:"/not-found/"})},controlDisplayTimer(t=null,e=!1){var s,i,a;const n=/iPhone|iPad|iPod|Windows|Macintosh|Android|Mobile/i.test(navigator.userAgent)&&"ontouchend"in document;if(1==n&&null!==t&&"mousemove"===t.type)return;if(0==n&&null!==t&&("touchmove"===t.type||"click"===t.type))return;window.clearTimeout(this.control_interval_id);const r=()=>{null!==this.player&&this.player.template.controller.classList.contains("dplayer-controller-comment")?this.control_interval_id=window.setTimeout(r,3e3):(this.is_control_display=!1,null!==this.player&&(this.player.controller.hide(),this.player.setting.hide()))};!0===n&&!0===e?(null===(s=this.player)||void 0===s?void 0:s.controller.isShow())?(this.is_control_display=!0,this.player.controller.show(),this.control_interval_id=window.setTimeout(r,3e3)):(this.is_control_display=!1,null===(i=this.player)||void 0===i||i.controller.hide(),null===(a=this.player)||void 0===a||a.setting.hide()):(this.is_control_display=!0,null!==this.player&&this.player.controller.show(),this.control_interval_id=window.setTimeout(r,3e3))},initPlayer(){if(window.mpegts=Js(),null!==this.player&&!0===this.player_can_be_destroyed){try{this.player.destroy()}catch(a){void 0!==this.player.plugins.mpegts&&this.player.plugins.mpegts.destroy()}this.player_can_be_destroyed=!1,this.player=null}const t=this.settingsStore.settings.tv_low_latency_mode?Di:Bi;this.player=new(Xs())({container:this.$el.querySelector(".watch-player__dplayer"),theme:"#E64F97",lang:"ja-jp",live:!0,liveSyncMinBufferSize:this.is_mpegts_supported?t-.1:0,loop:!1,airplay:!1,autoplay:!0,hotkey:!1,screenshot:!1,volume:1,video:{defaultQuality:this.channelsStore.channel.current.is_radiochannel?"48kHz/192kbps":this.settingsStore.settings.tv_streaming_quality,quality:(()=>{const t=[];if(this.channelsStore.channel.current.is_radiochannel)!0===this.is_mpegts_supported?t.push({name:"48kHz/192kbps",type:"mpegts",url:`${ht.api_base_url}/streams/live/${this.channelsStore.channel_id}/1080p/mpegts`}):t.push({name:"48kHz/192kbps",type:"live-llhls-for-KonomiTV",url:`${ht.api_base_url}/streams/live/${this.channelsStore.channel_id}/1080p/ll-hls`});else{let e="";at.isHEVCVideoSupported()&&!0===this.settingsStore.settings.tv_data_saver_mode&&(e="-hevc");for(const s of["1080p-60fps","1080p","810p","720p","540p","480p","360p","240p"])!0===this.is_mpegts_supported?t.push({name:"1080p-60fps"===s?"1080p (60fps)":s,type:"mpegts",url:`${ht.api_base_url}/streams/live/${this.channelsStore.channel_id}/${s}${e}/mpegts`}):t.push({name:"1080p-60fps"===s?"1080p (60fps)":s,type:"live-llhls-for-KonomiTV",url:`${ht.api_base_url}/streams/live/${this.channelsStore.channel_id}/${s}${e}/ll-hls`})}return t})()},danmaku:{user:"KonomiTV",speedRate:this.settingsStore.settings.comment_speed_rate,fontSize:this.settingsStore.settings.comment_font_size},apiBackend:{read:t=>{t.success([])},send:async t=>{this.$refs.Comment.sendComment(t)}},pluginOptions:{mpegts:{config:{enableWorker:!0,enableStashBuffer:!1,liveSync:this.settingsStore.settings.tv_low_latency_mode,liveSyncMaxLatency:3,liveSyncTargetLatency:t,liveSyncPlaybackRate:1.1}},aribb24:{normalFont:`"${this.settingsStore.settings.caption_font}", "Rounded M+ 1m for ARIB", sans-serif`,forceStrokeColor:this.settingsStore.settings.always_border_caption_text,forceBackgroundColor:(()=>{if(!0===this.settingsStore.settings.specify_caption_opacity){const t=this.settingsStore.settings.caption_opacity;return`rgba(0, 0, 0, ${t})`}return null})(),drcsReplacement:!0,enableRawCanvas:!0,useStroke:!0,usePUA:(()=>{const t=this.settingsStore.settings.caption_font,e=document.createElement("canvas").getContext("2d");return e.font='10px "Rounded M+ 1m for ARIB"',e.fillText("Test",0,0),e.font=`10px "${t}"`,e.fillText("Test",0,0),!!t.startsWith("Windows TV")})(),PRACallback:async t=>{if(!1===this.settingsStore.settings.tv_show_superimpose)return;"suspended"===this.romsounds_context.state&&await this.romsounds_context.resume();const e=this.romsounds_context.createBufferSource();e.buffer=this.romsounds_buffers[t];const s=this.romsounds_context.createGain();e.connect(s),s.connect(this.romsounds_context.destination),s.gain.value=3,e.start(0)}}},subtitle:{type:"aribb24"}}),window.player=this.player,this.player.controller.setAutoHide=t=>{},this.$refs.Comment.initSession(this.player,this.channelsStore.channel_id),this.player.template.commentInput.addEventListener("keydown",(t=>{"Enter"===t.code&&(this.is_comment_send_just_did=!0,setTimeout((()=>this.is_comment_send_just_did=!1),100))})),this.player.comment.send=()=>{null!==this.player&&(!0===this.settingsStore.settings.close_comment_form_after_sending&&this.player.template.commentInput.blur(),this.player.template.commentInput.value.replace(/^\s+|\s+$/g,"")?(this.player.danmaku.send({text:this.player.template.commentInput.value,color:this.player.container.querySelector(".dplayer-comment-setting-color input:checked").value,type:this.player.container.querySelector(".dplayer-comment-setting-type input:checked").value,size:this.player.container.querySelector(".dplayer-comment-setting-size input:checked").value},(()=>{!0===this.settingsStore.settings.close_comment_form_after_sending&&null!==this.player&&this.player.comment.hide()}),!0),this.player.template.commentInput.value=""):this.player.notice(this.player.tran("Please input danmaku content!")))};const e=/iPhone|iPad|iPod|Macintosh|Android|Mobile/i.test(navigator.userAgent)&&"ontouchend"in document;if(!1===e){this.player.template.settingOriginPanel.insertAdjacentHTML("beforeend",'\n
\n キーボードショートカット\n
\n \n \n \n
\n
');const t=this.player.template.settingOriginPanel.scrollHeight;this.player.template.settingBox.style.clipPath=`inset(calc(100% - ${t}px) 0 0 round 7px)`,this.$el.querySelector(".dplayer-setting-keyboard-shortcut").addEventListener("click",(()=>{var t;null===(t=this.player)||void 0===t||t.setting.hide(),this.shortcut_key_modal=!0}))}const s=document.querySelector(".v-application");this.fullscreen_handler=()=>{var t;this.is_fullscreen=!0===(null===(t=this.player)||void 0===t?void 0:t.fullScreen.isFullScreen())},void 0!==s.onfullscreenchange?s.addEventListener("fullscreenchange",this.fullscreen_handler):s.addEventListener("webkitfullscreenchange",this.fullscreen_handler),this.player.fullScreen.isFullScreen=t=>!(!document.fullscreenElement&&!document.webkitFullscreenElement),this.player.fullScreen.request=t=>{null!==this.player&&(this.player.fullScreen.isFullScreen()?this.player.fullScreen.cancel():(s.requestFullscreen=s.requestFullscreen||s.webkitRequestFullscreen,s.requestFullscreen?(s.requestFullscreen(),screen.orientation&&screen.orientation.lock("landscape").catch((()=>{}))):this.player.notice("iPhone Safari は動画のフルスクリーン表示に対応していません。")))},this.player.fullScreen.cancel=t=>{document.exitFullscreen=document.exitFullscreen||document.webkitExitFullscreen,document.exitFullscreen&&document.exitFullscreen(),screen.orientation&&screen.orientation.unlock()};const i=()=>{var t;null===(t=this.player)||void 0===t||t.setting.hide(),this.controlDisplayTimer()};this.player.on("play",i),this.player.on("pause",i),this.player.on("quality_start",(()=>{this.background_url=at.generatePlayerBackgroundURL(),null!==this.eventsource&&(this.eventsource.close(),this.eventsource=null),this.initEventHandler()})),!0===this.is_mpegts_supported?this.interval_ids.push(window.setInterval((()=>{null!==this.player&&this.player.video.paused&&this.player.video.buffered.length>=1&&this.player.video.buffered.end(0)-this.player.video.currentTime>30&&this.player.sync()}),6e4)):this.interval_ids.push(window.setInterval((()=>{null!==this.player&&this.player.video.paused&&this.player.sync()}),3e4)),(async()=>{if(null!==this.player){while(void 0===this.player.plugins.aribb24Superimpose)await ht.sleep(.1);!0===this.settingsStore.settings.tv_show_superimpose?(this.player.plugins.aribb24Superimpose.show(),this.player.on("subtitle_hide",(()=>{var t;null===(t=this.player)||void 0===t||t.plugins.aribb24Superimpose.show()}))):(this.player.plugins.aribb24Superimpose.hide(),this.player.on("subtitle_show",(()=>{var t;null===(t=this.player)||void 0===t||t.plugins.aribb24Superimpose.hide()})))}})()},initEventHandler(){if(null===this.player)return;if(this.is_loading=!0,this.player.video.volume=0,this.player.video.crossOrigin="anonymous",!0===this.is_mpegts_supported&&void 0!==this.player.plugins.mpegts?this.player.plugins.mpegts.on(Js().Events.ERROR,(async(t,e)=>{this.player.notice(`再生中にエラーが発生しました。(${t}: ${e}) 3秒後にリロードします。`,-1),await ht.sleep(3),location.reload()})):!1===this.is_mpegts_supported&&this.player.on("error",(async()=>{this.player.notice(`再生中にエラーが発生しました。(${this.player.video.error.code}: ${this.player.video.error.message}) 3秒後にリロードします。`,-1),await ht.sleep(3),location.reload()})),!1===this.is_mpegts_supported){const t=()=>{var e,s;null===(e=this.player)||void 0===e||e.video.play().catch((()=>{console.warn("HTMLVideoElement.play() rejected. run fallback.");const t='';this.player.template.playButton.innerHTML=t,this.player.template.mobilePlayButton.innerHTML=t,this.player.container.classList.remove("dplayer-paused"),this.player.container.classList.add("dplayer-playing"),this.player.danmaku.play()})),!1!==this.is_loading||null===(s=this.player)||void 0===s||s.video.removeEventListener("pause",t)};this.player.video.addEventListener("pause",t)}const t=async()=>{if(null===this.player)return;if(this.player.video.oncanplay=null,this.player.video.oncanplaythrough=null,!0===this.is_mpegts_supported){this.player.video.playbackRate=0;const t=()=>{let t=0;return this.player.video.buffered.length>=1&&(t=this.player.video.buffered.end(0)),Math.round(1e3*(t-this.player.video.currentTime))/1e3},e=this.settingsStore.settings.tv_low_latency_mode?Di:Bi;let s=t();while(s{var t,e,s;await ht.sleep(.5),(null===(t=this.player)||void 0===t?void 0:t.video.readyState)<3&&(console.log("player.video.readyState < HAVE_FUTURE_DATA. trying to recover."),null===(e=this.player)||void 0===e||e.video.pause(),await ht.sleep(.1),null===(s=this.player)||void 0===s||s.video.play().catch((()=>{var t;console.warn("HTMLVideoElement.play() rejected. paused."),null===(t=this.player)||void 0===t||t.pause()})))};this.player.video.addEventListener("waiting",(()=>this.is_video_buffering=!0)),this.player.video.addEventListener("playing",(()=>{this.is_video_buffering=!1,t()})),this.is_loading=!1,this.is_video_buffering=!1,t(),this.channelsStore.channel.current.is_radiochannel?this.is_background_display=!0:this.is_background_display=!1;const e=this.player.user.get("volume");while(this.player.video.volume+.05{const e=JSON.parse(t.data);switch(console.log(`[initial_update] Status: ${e.status} / Detail: ${e.detail}`),e.status){case"Standby":this.is_video_buffering=!0,this.is_background_display=!0;break}})),this.eventsource.addEventListener("status_update",(async e=>{var s,i;if(null===this.player)return;const a=JSON.parse(e.data);switch(console.log(`[status_update] Status: ${a.status} / Detail: ${a.detail}`),this.channelsStore.updateChannel(this.channelsStore.channel_id,Object.assign(Object.assign({},this.channelsStore.channel.current),{viewers:a.clients_count})),a.status){case"Standby":this.player.template.notice.textContent.includes("画質を")||this.player.notice(a.detail,-1),this.is_video_buffering=!0,this.is_background_display=!0;break;case"ONAir":this.player.template.notice.textContent.includes("画質を")||this.player.notice(this.player.template.notice.textContent,1e-6),!1===this.is_mpegts_supported&&(this.player.video.load(),this.player.video.play(),t()),this.player.container.classList.contains("dplayer-paused")&&(this.player.container.classList.remove("dplayer-paused"),this.player.container.classList.add("dplayer-playing")),document.pictureInPictureElement&&(document.exitPictureInPicture(),this.player.video.requestPictureInPicture());break;case"Idling":this.player.notice("ストリーミング接続が切断されました。3秒後にリロードします。",-1),await ht.sleep(3),location.reload();break;case"Restart":"ライブストリームは Offline です。"===a.detail&&(this.player.notice("ストリーミング接続が切断されました。3秒後にリロードします。",-1),await ht.sleep(3),location.reload()),this.player.notice(a.detail,-1),this.player.switchVideo({url:this.player.quality.url,type:this.player.quality.type}),this.player.play(),this.is_video_buffering=!0,this.is_background_display=!0;break;case"Offline":null!==this.player&&(this.player.notice(a.detail,-1),this.player.video.onerror=()=>{this.player.notice(a.detail,-1),this.player.video.onerror=null},null===(i=null===(s=this.player)||void 0===s?void 0:s.danmaku)||void 0===i||i.clear(),this.player.video.pause()),null!==this.eventsource&&(this.eventsource.close(),this.eventsource=null),this.is_background_display=!0,this.is_loading=!1,this.is_video_buffering=!1;break}})),this.eventsource.addEventListener("detail_update",(t=>{if(null===this.player)return;const e=JSON.parse(t.data);switch(console.log(`[detail_update] Status: ${e.status} Detail:${e.detail}`),this.channelsStore.updateChannel(this.channelsStore.channel_id,Object.assign(Object.assign({},this.channelsStore.channel.current),{viewers:e.clients_count})),e.status){case"Standby":this.player.notice(e.detail,-1),this.is_background_display||(this.is_background_display=!0);break}})),this.eventsource.addEventListener("clients_update",(t=>{const e=JSON.parse(t.data);this.channelsStore.updateChannel(this.channelsStore.channel_id,Object.assign(Object.assign({},this.channelsStore.channel.current),{viewers:e.clients_count}))}))},initShortcutKeyHandler(){const t=this.$refs.Twitter,e=t.$el.querySelector(".tweet-form__textarea");for(const s of document.querySelectorAll("input[type=text],input[type=search],textarea"))s.addEventListener("compositionstart",(()=>this.is_ime_composing=!0)),s.addEventListener("compositionend",(()=>this.is_ime_composing=!1));this.shortcut_key_handler=async s=>{const i=document.activeElement.tagName.toUpperCase(),a=document.activeElement.getAttribute("contenteditable");["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(s.code)&&"INPUT"!==i&&"TEXTAREA"!==i&&""!==a&&"true"!==a&&s.preventDefault();let n=!1;s.repeat&&(n=!0);const r=ht.time();if(r-this.shortcut_key_pressed_at<.05)return;this.shortcut_key_pressed_at=r;const o=await(async()=>{if(("INPUT"!==i&&"TEXTAREA"!==i&&""!==a&&"true"!==a||document.activeElement===e)&&!1===this.is_ime_composing&&"Tab"===s.code)return document.activeElement===e?(e.blur(),!0):(this.is_panel_display=!0,this.tv_panel_active_tab="Twitter",e.focus(),this.$el.scrollLeft=0,window.setTimeout((()=>{e.focus(),this.$el.scrollLeft=0}),100),!0);if(("INPUT"!==i&&"TEXTAREA"!==i&&""!==a&&"true"!==a||document.activeElement===e)&&"Twitter"===this.tv_panel_active_tab&&!1===this.is_ime_composing&&(s.ctrlKey||s.metaKey||s.shiftKey)&&"Enter"===s.code)return t.$el.querySelector(".tweet-button").click(),!0;if(null!==this.player&&!s.shiftKey&&!s.altKey&&this.player.template.controller.classList.contains("dplayer-controller-comment")&&(s.ctrlKey||s.metaKey)&&"KeyM"===s.code)return this.player.comment.hide(),!0;if("INPUT"!==i&&"TEXTAREA"!==i&&""!==a&&"true"!==a){if(!1===n&&!s.ctrlKey&&!s.metaKey&&(!1===this.settingsStore.settings.tv_channel_selection_requires_alt_key||s.altKey)){const t=s.shiftKey?"BS":"GR";let e=null;if("Digit1"!==s.code&&"Digit2"!==s.code&&"Digit3"!==s.code&&"Digit4"!==s.code&&"Digit5"!==s.code&&"Digit6"!==s.code&&"Digit7"!==s.code&&"Digit8"!==s.code&&"Digit9"!==s.code||(e=Number(s.code.replace("Digit",""))),"Digit0"===s.code&&(e=10),"Minus"===s.code&&(e=11),"Equal"===s.code&&(e=12),"Numpad1"!==s.code&&"Numpad2"!==s.code&&"Numpad3"!==s.code&&"Numpad4"!==s.code&&"Numpad5"!==s.code&&"Numpad6"!==s.code&&"Numpad7"!==s.code&&"Numpad8"!==s.code&&"Numpad9"!==s.code||(e=Number(s.code.replace("Numpad",""))),"Numpad0"===s.code&&(e=10),null!==e){const s=this.channelsStore.getChannelByRemoconID(t,e);if(null!==s&&s.channel_id!==this.channelsStore.channel_id)return await this.$router.push({path:`/tv/watch/${s.channel_id}`}),!0}}if(!1===n&&!s.ctrlKey&&!s.metaKey&&!s.shiftKey&&!s.altKey){if("Slash"===s.code)return this.shortcut_key_modal=!this.shortcut_key_modal,!0;if("KeyP"===s.code)return this.is_panel_display=!this.is_panel_display,!0;if("KeyK"===s.code)return this.tv_panel_active_tab="Program",!0;if("KeyL"===s.code)return this.tv_panel_active_tab="Channel",!0;if("Semicolon"===s.code)return this.tv_panel_active_tab="Comment",!0;if("Quote"===s.code)return this.tv_panel_active_tab="Twitter",!0;if("BracketRight"===s.code)return t.twitter_active_tab="Search",!0;if("Backslash"===s.code)return t.twitter_active_tab="Timeline",!0;if("IntlRo"===s.code)return t.twitter_active_tab="Capture",!0}if("Twitter"===this.tv_panel_active_tab&&"Capture"===t.twitter_active_tab&&!s.ctrlKey&&!s.metaKey&&!s.shiftKey&&!s.altKey){if(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(s.code)){if(0===t.captures.length)return!1;if(!1===t.captures.some((t=>!0===t.focused)))return t.captures[t.captures.length-1].focused=!0,!0;const e=t.captures.findIndex((t=>!0===t.focused));if("ArrowUp"===s.code){if(e-2<0)return!1;t.captures[e-2].focused=!0}if("ArrowDown"===s.code){if(e+2>t.captures.length-1)return!1;t.captures[e+2].focused=!0}if("ArrowLeft"===s.code){if(e-1<0)return!1;t.captures[e-1].focused=!0}if("ArrowRight"===s.code){if(e+1>t.captures.length-1)return!1;t.captures[e+1].focused=!0}t.captures[e].focused=!1;const i=t.captures.find((t=>!0===t.focused));!0===t.zoom_capture_modal&&(t.zoom_capture=i);const a=t.$el.querySelector(`img[src="${i.image_url}"]`).parentElement;return n?a.scrollIntoView({block:"nearest",inline:"nearest",behavior:"auto"}):a.scrollIntoView({block:"nearest",inline:"nearest",behavior:"smooth"}),!0}if("Enter"===s.code){if(this.is_comment_send_just_did)return!1;if(!0===t.zoom_capture_modal)return t.zoom_capture_modal=!1,!0;const e=t.captures.find((t=>!0===t.focused));return void 0!==e&&(t.zoom_capture=e,t.zoom_capture_modal=!0,!0)}if("Space"===s.code){const e=t.captures.find((t=>!0===t.focused));return void 0!==e&&(t.clickCapture(e),!0)}}if(!1===n&&!s.ctrlKey&&!s.metaKey&&!s.shiftKey&&!s.altKey){if("ArrowUp"===s.code)return this.is_zapping=!0,await this.$router.push({path:`/tv/watch/${this.channelsStore.channel.previous.channel_id}`}),!0;if("ArrowDown"===s.code)return this.is_zapping=!0,await this.$router.push({path:`/tv/watch/${this.channelsStore.channel.next.channel_id}`}),!0}if(null!==this.player&&!s.shiftKey&&!s.altKey){if((s.ctrlKey||s.metaKey)&&"ArrowUp"===s.code)return this.player.volume(this.player.volume()+.05),!0;if((s.ctrlKey||s.metaKey)&&"ArrowDown"===s.code)return this.player.volume(this.player.volume()-.05),!0;if((s.ctrlKey||s.metaKey)&&"ArrowLeft"===s.code)return!1===this.player.video.paused&&this.player.video.pause(),this.player.video.currentTime=this.player.video.currentTime-.5,!0;if((s.ctrlKey||s.metaKey)&&"ArrowRight"===s.code)return!1===this.player.video.paused&&this.player.video.pause(),this.player.video.currentTime=this.player.video.currentTime+.5,!0}if(null!==this.player&&!s.ctrlKey&&!s.metaKey&&!s.altKey&&!0===s.shiftKey&&"Space"===s.code&&!1===n&&"Twitter"===this.tv_panel_active_tab&&"Capture"===t.twitter_active_tab)return this.player.toggle(),!0;if(null!==this.player&&!1===n&&!s.ctrlKey&&!s.metaKey&&!s.altKey){if("Space"===s.code)return this.player.toggle(),!0;if("KeyF"===s.code)return this.player.fullScreen.toggle(),!0;if("KeyW"===s.code)return this.player.sync(),!0;if("KeyE"===s.code)return document.pictureInPictureEnabled&&this.player.template.pipButton.click(),!0;if("KeyS"===s.code)return this.player.subtitle.toggle(),this.player.subtitle.container.classList.contains("dplayer-subtitle-hide")?this.player.notice(`${this.player.tran("Hide subtitle")}`):this.player.notice(`${this.player.tran("Show subtitle")}`),!0;if("KeyD"===s.code)return this.player.template.showDanmaku.click(),this.player.template.showDanmakuToggle.checked?this.player.notice(`${this.player.tran("Show comment")}`):this.player.notice(`${this.player.tran("Hide comment")}`),!0;if("KeyC"===s.code)return await this.capture_handler.captureAndSave(!1),!0;if("KeyV"===s.code)return await this.capture_handler.captureAndSave(!0),!0;if("KeyM"===s.code)return this.player.controller.show(),this.player.comment.show(),this.controlDisplayTimer(),window.setTimeout((()=>this.player.template.commentInput.focus()),100),!0}}return!1})();!0===o&&s.preventDefault()},document.addEventListener("keydown",this.shortcut_key_handler)},initCaptureHandler(){this.capture_handler=new Ai(this.player,((t,e)=>{this.$refs.Twitter.addCaptureList(t,e)}));const t=this.$el.querySelector(".dplayer-icon.dplayer-capture-icon");t.addEventListener("click",(async()=>{await this.capture_handler.captureAndSave(!1)}));const e=this.$el.querySelector(".dplayer-icon.dplayer-comment-capture-icon");e.addEventListener("click",(async()=>{await this.capture_handler.captureAndSave(!0)}))},async destroy(t=!1,e=!1){this.$refs.Comment.destroy();for(const i of this.interval_ids)window.clearInterval(i);if(window.clearTimeout(this.control_interval_id),this.interval_ids=[],this.is_loading=!0,this.is_background_display=!1,null!==this.player&&(this.player_can_be_destroyed=!0),null!==this.eventsource&&(this.eventsource.close(),this.eventsource=null),!1===e){const t=this.player.user.get("volume");for(let e=0;e<20;e++)await ht.sleep(.01),this.player.video.volume=t*(1-(e+1)/20)}if(!0===t&&null!==this.player){try{this.player.destroy()}catch(s){void 0!==this.player.plugins.mpegts&&this.player.plugins.mpegts.destroy()}this.player_can_be_destroyed=!1,this.player=null}}}}),Ki=Ni,Li=(0,h.Z)(Ki,Gs,Ws,!1,null,"51acc2dc",null),Ei=Li.exports;o["default"].use(z.ZP);const Hi=new z.ZP({mode:"history",base:"/",routes:[{path:"/",redirect:"/tv/"},{path:"/tv/",name:"TV Home",component:Vs},{path:"/tv/watch/:channel_id",name:"TV Watch",component:Ei},{path:"/settings/",name:"Settings Index",component:es,beforeEnter:(t,e,s)=>{ht.isSmartphoneVertical()||ht.isSmartphoneHorizontal()||ht.isTabletVertical()?s():s({path:"/settings/general/"})}},{path:"/settings/general",name:"Settings General",component:qe},{path:"/settings/caption",name:"Settings Caption",component:De},{path:"/settings/capture",name:"Settings Capture",component:He},{path:"/settings/account",name:"Settings Account",component:Te},{path:"/settings/jikkyo",name:"Settings Jikkyo",component:ps},{path:"/settings/twitter",name:"Settings Twitter",component:Zs},{path:"/settings/server",name:"Settings Server",component:bs},{path:"/login/",name:"Login",component:qt},{path:"/register/",name:"Register",component:oe},{path:"*",name:"NotFound",component:ee}],scrollBehavior(t,e,s){return s||{x:0,y:0}}});var Mi=Hi,Ui=s(5205);(0,Ui.z)("/service-worker.js",{ready(){console.log("App is being served from cache by a service worker.\nFor more details, visit https://goo.gl/AFskqB")},registered(){console.log("Service worker has been registered.")},cached(){console.log("Content has been cached for offline use.")},updatefound(){console.log("New content is downloading.")},updated(t){console.log("New content is available; please refresh."),K.show({message:"クライアントが新しいバージョンに更新されました。5秒後にリロードします。",timeout:1e4}),t.waiting.postMessage({type:"SKIP_WAITING"}),t.waiting.addEventListener("statechange",(async t=>{"activated"===t.target.state&&(await ht.sleep(4),location.reload(!0))}))},offline(){console.log("No internet connection found. App is running in offline mode.")},error(t){console.error("Error during service worker registration:",t)}}),(0,n.OK)(),o["default"].config.productionTip=!1,o["default"].config.devtools=!0,o["default"].use(a.og);const Vi=(0,a.WB)();o["default"].use(l.ZP),o["default"].use(_(),{top:!1,bottom:!0,color:"#433532",dark:!0,elevation:8,timeout:2500,autoRemove:!0,closeButtonContent:"閉じる",vuetifyInstance:Z});const Ri=ht.isTouchDevice()?[]:["hover","focus","touch"];r.ZP.options.themes.tooltip.showTriggers=Ri,r.ZP.options.themes.tooltip.hideTriggers=Ri,r.ZP.options.themes.tooltip.delay.show=0,r.ZP.options.offset=[0,7],o["default"].use(r.ZP),o["default"].component("Icon",i.JO),o["default"].component("v-tab-item-fix",w),o["default"].component("v-tabs-fix",x),o["default"].component("v-tabs-items-fix",O),window.KonomiTVVueInstance=new o["default"]({pinia:Vi,router:Mi,vuetify:Z,render:t=>t(v)}).$mount("#app");let Fi=!1;const Gi=st();Gi.$subscribe((async()=>{!0!==Fi&&(console.log("Client Settings Changed:",Gi.settings),Q(Gi.settings),await Gi.syncClientSettingsToServer())}),{detached:!0}),window.setInterval((async()=>{null!==ht.getAccessToken()&&!0===Gi.settings.sync_settings&&(Fi=!0,await Gi.syncClientSettingsFromServer(),Fi=!1,Q(Gi.settings))}),3e3)}},e={};function s(i){var a=e[i];if(void 0!==a)return a.exports;var n=e[i]={exports:{}};return t[i].call(n.exports,n,n.exports,s),n.exports}s.m=t,function(){var t=[];s.O=function(e,i,a,n){if(!i){var r=1/0;for(_=0;_=n)&&Object.keys(s.O).every((function(t){return s.O[t](i[l])}))?i.splice(l--,1):(o=!1,n0&&t[_-1][2]>n;_--)t[_]=t[_-1];t[_]=[i,a,n]}}(),function(){s.n=function(t){var e=t&&t.__esModule?function(){return t["default"]}:function(){return t};return s.d(e,{a:e}),e}}(),function(){s.d=function(t,e){for(var i in e)s.o(e,i)&&!s.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})}}(),function(){s.g=function(){if("object"===typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"===typeof window)return window}}()}(),function(){s.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)}}(),function(){s.r=function(t){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}}(),function(){var t={143:0};s.O.j=function(e){return 0===t[e]};var e=function(e,i){var a,n,r=i[0],o=i[1],l=i[2],c=0;if(r.some((function(e){return 0!==t[e]}))){for(a in o)s.o(o,a)&&(s.m[a]=o[a]);if(l)var _=l(s)}for(e&&e(i);c {\n\n // VueComponent の key が一致する this.$slots.default 内の VNode を探す\n const index_a = this.$slots.default.findIndex((element) => {\n return a.$vnode.key === element.key;\n });\n const index_b = this.$slots.default.findIndex((element) => {\n return b.$vnode.key === element.key;\n });\n\n // index 順で並び替え\n return index_a - index_b;\n });\n\n item.$on('change', () => (this as any).onClick(item));\n if ((this as any).mandatory && !(this as any).selectedValues.length) {\n (this as any).updateMandatory();\n }\n\n // 追加された要素のソート後のインデックスを取得して更新する\n (this as any).updateItem(item, this.items.indexOf(item));\n\n // ソート後の現在アクティブなタブのインデックスを取得し直し、設定する\n // 配列の末尾以外に追加された場合はインデックスが1つずつずれてしまうため、インデックスを設定し直す必要がある\n if (activeItem !== undefined) {\n (this as any).updateInternalValue(this.items.indexOf(activeItem));\n }\n },\n\n unregister(item: GroupableInstance) {\n\n // 現在アクティブなタブの VueComponent を取得\n const activeItem = this.items[(this as any).internalIndex];\n\n // 継承元の unregister() の処理を呼び出す(いわゆる super() )\n // ref: https://github.com/vuejs/vue/issues/2977\n (this.constructor as any).super.options.methods.unregister.call(this, item);\n\n // 配列の末尾以外から削除された場合はインデックスが1つずつずれてしまうため、インデックスを設定し直す必要がある\n if (activeItem !== undefined) {\n (this as any).updateInternalValue(this.items.indexOf(activeItem));\n }\n }\n }\n});\n","\nimport { VueConstructor, VNode } from 'vue';\nimport VTabs from 'vuetify/lib/components/VTabs/VTabs';\nimport { convertToUnit } from 'vuetify/lib/util/helpers';\n\nimport VTabsBar from '@/components/Vuetify/VTabsBar';\n\nexport default (VTabs as VueConstructor).extend({\n methods: {\n\n // VTabsBar は VTabs から暗黙的に生成されるコンポーネントのため、直接上書きすることができない\n // そこで VTabs 自体も上書きし、VTabs で $createElement() される時の VTabsBar を自前でオーバーライドしたものに差し替える\n // ビルド済みのファイルには型定義が入っていないので any を多用せざるを得ない…\n genBar(items: VNode[], slider: VNode | null) {\n const data = {\n style: {\n height: convertToUnit((this as any).height),\n },\n props: {\n activeClass: (this as any).activeClass,\n centerActive: (this as any).centerActive,\n dark: (this as any).dark,\n light: (this as any).light,\n mandatory: !(this as any).optional,\n mobileBreakpoint: (this as any).mobileBreakpoint,\n nextIcon: (this as any).nextIcon,\n prevIcon: (this as any).prevIcon,\n showArrows: (this as any).showArrows,\n value: (this as any).internalValue,\n },\n on: {\n 'call:slider': (this as any).callSlider,\n change: (val: any) => {\n (this as any).internalValue = val;\n },\n },\n ref: 'items',\n };\n\n (this as any).setTextColor((this as any).computedColor, data);\n (this as any).setBackgroundColor((this as any).backgroundColor, data);\n\n // ここでオーバーライドした VTabsBar を使うのが最重要\n // これをやるためだけにわざわざ VTabs に関してもオーバーライドする羽目になってる…\n return (this as any).$createElement(VTabsBar, data, [\n (this as any).genSlider(slider),\n items,\n ]);\n }\n }\n});\n","\nimport { VueConstructor } from 'vue';\nimport { GroupableInstance } from 'vuetify/lib/components/VItemGroup/VItemGroup';\nimport VTabsItems from 'vuetify/lib/components/VTabs/VTabsItems';\n\n// VTabsItems は VItemGroup と VWindow を extend() して実装されている\nexport default (VTabsItems as VueConstructor).extend({\n data() {\n return {\n // 一応型定義をしておく\n items: [] as GroupableInstance[],\n };\n },\n methods: {\n\n // タブのデータ配列の先頭に新しい要素が追加されるとそのタブのアニメーションの向きが逆になるバグがあるので、VItemGroup 側の挙動をオーバーライドする\n // DOM 上も VNode 上も正しい順序で並んでいるが、this.items に関しては追加された順になっていてしまっていて齟齬が発生するのが原因\n // ref: https://github.com/vuetifyjs/vuetify/issues/13862\n register(item: GroupableInstance) {\n\n // 現在アクティブなタブの VueComponent を取得\n const activeItem = this.items[(this as any).internalIndex];\n\n // 要素を items に追加\n this.items.push(item);\n\n // this.$slots.default に VNode が、items には単に VueComponent が入っているので、事前に VNode の順番に合わせて並べ替える\n // こうすることで、追加された順ではなく元のデータ配列通りの順番になる\n this.items.sort((a, b) => {\n\n // VueComponent の key が一致する this.$slots.default 内の VNode を探す\n const index_a = this.$slots.default.findIndex((element) => {\n return a.$vnode.key === element.key;\n });\n const index_b = this.$slots.default.findIndex((element) => {\n return b.$vnode.key === element.key;\n });\n\n // index 順で並び替え\n return index_a - index_b;\n });\n\n item.$on('change', () => (this as any).onClick(item));\n if ((this as any).mandatory && !(this as any).selectedValues.length) {\n (this as any).updateMandatory();\n }\n\n // 追加された要素のソート後のインデックスを取得して更新する\n (this as any).updateItem(item, this.items.indexOf(item));\n\n // ソート後の現在アクティブなタブのインデックスを取得し直し、設定する\n // 配列の末尾以外に追加された場合はインデックスが1つずつずれてしまうため、インデックスを設定し直す必要がある\n if (activeItem !== undefined) {\n // 値が異なるときだけ更新する\n // こうしないと、Safari で変なアニメーションがついてしまう\n if (this.items.indexOf(activeItem) !== (this as any).internalValue) {\n (this as any).updateInternalValue(this.items.indexOf(activeItem));\n }\n }\n },\n\n unregister(item: GroupableInstance) {\n\n // 現在アクティブなタブの VueComponent を取得\n const activeItem = this.items[(this as any).internalIndex];\n\n // 継承元の unregister() の処理を呼び出す(いわゆる super() )\n // ref: https://github.com/vuejs/vue/issues/2977\n (this.constructor as any).super.options.methods.unregister.call(this, item);\n\n // 配列の末尾以外から削除された場合はインデックスが1つずつずれてしまうため、インデックスを設定し直す必要がある\n if (activeItem !== undefined) {\n (this as any).updateInternalValue(this.items.indexOf(activeItem));\n }\n },\n\n // 最初のタブから最後のタブに遷移するとアニメーションの向きが逆になるバグがあるので、VWindow 側の挙動をオーバーライドする\n // 本来は VCarousel 用の動作だが、VTabsItems も VWindow を継承しているので、それが適用されてしまっているらしい\n // ref: https://github.com/yuwu9145/vuetify/blob/master/packages/vuetify/src/components/VWindow/VWindow.ts#L239-L252\n updateReverse(val: number, oldVal: number) {\n\n const itemsLength = this.items.length;\n const lastIndex = itemsLength - 1;\n\n if (itemsLength <= 2) return val < oldVal;\n\n // continuous が false の時、常に val < oldVal の結果を返す\n if (!(this as any).continuous) return val < oldVal;\n\n if (val === lastIndex && oldVal === 0) {\n return true;\n } else if (val === 0 && oldVal === lastIndex) {\n return false;\n } else {\n return val < oldVal;\n }\n }\n }\n});\n","\nimport Vue from 'vue';\nimport { VSnackbar, VBtn, VIcon } from 'vuetify/lib';\nimport Vuetify from 'vuetify/lib/framework';\n\n\nVue.use(Vuetify);\n\n// vuetify-message-snackbar を使うのに必要\nVue.component('v-snackbar', VSnackbar);\nVue.component('v-btn', VBtn);\nVue.component('v-icon', VIcon);\n\nexport default new Vuetify({\n theme: {\n dark: true,\n themes: {\n dark: {\n primary: '#E64F97',\n secondary: '#E33157',\n twitter: {\n base: '#4F82E6',\n lighten1: '#799FEC',\n lighten2: '#41A5F1',\n },\n gray: '#66514C',\n black: '#110A09',\n background: {\n base: '#1E1310',\n lighten1: '#2F221F',\n lighten2: '#433532',\n lighten3: '#4c3c38',\n },\n text: {\n base: '#FFEAEA',\n darken1: '#D9C7C7',\n darken2: '#8E7F7E',\n darken3: '#786968',\n }\n }\n },\n options: {\n customProperties: true,\n },\n },\n});\n","\n/**\n * 共通ユーティリティ\n */\nexport default class Utils {\n\n // バージョン情報\n // ビルド時の環境変数 (vue.config.js に記載) から取得\n static readonly version: string = process.env.VUE_APP_VERSION;\n\n // バックエンドの API のベース URL\n static readonly api_base_url = (() => {\n if (process.env.NODE_ENV === 'development') {\n // デバッグ時はポートを 7000 に強制する\n return `${window.location.protocol}//${window.location.hostname}:7000/api`;\n } else {\n // ビルド後は同じポートを使う\n return `${window.location.protocol}//${window.location.host}/api`;\n }\n })();\n\n\n /**\n * アクセストークンを LocalStorage から取得する\n * @returns JWT アクセストークン(ログインしていない場合は null が返る)\n */\n static getAccessToken(): string | null {\n\n // LocalStorage の取得結果をそのまま返す\n // LocalStorage.getItem() はキーが存在しなければ(=ログインしていなければ)null を返す\n return localStorage.getItem('KonomiTV-AccessToken');\n }\n\n\n /**\n * アクセストークンを LocalStorage に保存する\n * @param access_token 発行された JWT アクセストークン\n */\n static saveAccessToken(access_token: string): void {\n\n // そのまま LocalStorage に保存\n localStorage.setItem('KonomiTV-AccessToken', access_token);\n }\n\n\n /**\n * アクセストークンを LocalStorage から削除する\n * アクセストークンを削除することで、ログアウト相当になる\n */\n static deleteAccessToken(): void {\n\n // LocalStorage に KonomiTV-AccessToken キーが存在しない\n if (localStorage.getItem('KonomiTV-AccessToken') === null) return;\n\n // KonomiTV-AccessToken キーを削除\n localStorage.removeItem('KonomiTV-AccessToken');\n }\n\n\n /**\n * ブラウザが実行されている OS に応じて、\"Alt\" または \"Option\" を返す\n * キーボードショートカットのコンビネーションキーの説明を OS によって分けるために使う\n * @returns ブラウザが実行されている OS が Mac なら Option を、それ以外なら Alt を返す\n */\n static AltOrOption(): 'Alt' | 'Option' {\n // iPhone・iPad で純正キーボードを接続した場合も一応想定して、iPhone・iPad も含める(動くかは未検証)\n return /iPhone|iPad|Macintosh/i.test(navigator.userAgent) ? 'Option' : 'Alt';\n }\n\n\n /**\n * ブラウザが実行されている OS に応じて、\"Ctrl\" または \"Cmd\" を返す\n * キーボードショートカットのコンビネーションキーの説明を OS によって分けるために使う\n * @returns ブラウザが実行されている OS が Mac なら Cmd を、それ以外なら Ctrl を返す\n */\n static CtrlOrCmd(): 'Ctrl' | 'Cmd' {\n // iPhone・iPad で純正キーボードを接続した場合も一応想定して、iPhone・iPad も含める(動くかは未検証)\n return /iPhone|iPad|Macintosh/i.test(navigator.userAgent) ? 'Cmd' : 'Ctrl';\n }\n\n\n /**\n * Blob に格納されているデータをブラウザにダウンロードさせる\n * @param blob Blob オブジェクト\n * @param filename 保存するファイル名\n */\n static downloadBlobData(blob: Blob, filename: string): void {\n\n // Blob URL を発行\n const blob_url = URL.createObjectURL(blob);\n\n // 画像をダウンロード\n const link = document.createElement('a');\n link.download = filename;\n link.href = blob_url;\n link.click();\n\n // Blob URL を破棄\n URL.revokeObjectURL(blob_url);\n }\n\n\n /**\n * innerHTML しても問題ないように HTML の特殊文字をエスケープする\n * PHP の htmlspecialchars() と似たようなもの\n * @param content HTML エスケープされてないテキスト\n * @returns HTML エスケープされたテキスト\n */\n static escapeHTML(content: string): string {\n\n // HTML エスケープが必要な文字\n // ref: https://www.php.net/manual/ja/function.htmlspecialchars.php\n const html_escape_table = {\n '&': '&',\n '\"': '"',\n '\\'': ''',\n '<': '<',\n '>': '>',\n };\n\n // ref: https://qiita.com/noriaki/items/4bfef8d7cf85dc1035b3\n return content.replace(/[&\"'<>]/g, (match) => {\n return html_escape_table[match];\n });\n }\n\n\n /**\n * OAuth 連携時のポップアップを画面中央に表示するための windowFeatures 文字列を取得する\n * ref: https://qiita.com/catatsuy/items/babce8726ea78f5d25b1\n * @returns window.open() で使う windowFeatures 文字列\n */\n static getWindowFeatures(): string {\n\n // ポップアップウインドウのサイズ\n const popupSizeWidth = 650;\n const popupSizeHeight = window.screen.height >= 800 ? 800 : window.screen.height - 100;\n\n // ポップアップウインドウの位置\n const posTop = (window.screen.height - popupSizeHeight) / 2;\n const posLeft = (window.screen.width - popupSizeWidth) / 2;\n\n return `toolbar=0,status=0,top=${posTop},left=${posLeft},width=${popupSizeWidth},height=${popupSizeHeight},modal=yes,alwaysRaised=yes`;\n }\n\n\n /**\n * 現在フォーカスを持っている要素に指定された CSS クラスが付与されているか\n * @param class_name 存在を確認する CSS クラス名\n * @returns document.activeElement が class_name で指定したクラスを持っているかどうか\n */\n static hasActiveElementClass(class_name: string): boolean {\n if (document.activeElement === null) return false;\n return document.activeElement.classList.contains(class_name);\n }\n\n\n /**\n * ブラウザが Firefox かどうか\n * @returns ブラウザが Firefox なら true を返す\n */\n static isFirefox(): boolean {\n return /Firefox/i.test(navigator.userAgent);\n }\n\n\n /**\n * モバイルデバイス(スマホ・タブレット)かどうか\n * @returns モバイルデバイス (スマホ・タブレット) なら true を返す\n */\n static isMobileDevice(): boolean {\n // Macintosh が入っているのは、iPadOS は既定でデスクトップ表示モードが有効になっていて、UA だけでは Mac と判別できないため\n // Mac にタッチパネル付きの機種は存在しないので、'ontouchend' in document で判定できる\n return /iPhone|iPad|iPod|Macintosh|Android|Mobile/i.test(navigator.userAgent) && 'ontouchend' in document;\n }\n\n\n /**\n * 表示画面がスマホ横画面かどうか\n * @returns スマホ横画面なら true を返す\n */\n static isSmartphoneHorizontal(): boolean {\n return window.matchMedia('(max-width: 1000px) and (max-height: 450px)').matches;\n }\n\n\n /**\n * 表示画面がスマホ縦画面かどうか\n * @returns スマホ縦画面なら true を返す\n */\n static isSmartphoneVertical(): boolean {\n return window.matchMedia('(max-width: 600px) and (min-height: 450.01px)').matches;\n }\n\n\n /**\n * 表示画面がタブレット横画面かどうか\n * @returns タブレット横画面なら true を返す\n */\n static isTabletHorizontal(): boolean {\n return window.matchMedia('(max-width: 1264px) and (max-height: 850px)').matches;\n }\n\n\n /**\n * 表示画面がタブレット縦画面かどうか\n * @returns タブレット縦画面なら true を返す\n */\n static isTabletVertical(): boolean {\n return window.matchMedia('(max-width: 850px) and (min-height: 850.01px)').matches;\n }\n\n\n /**\n * 表示端末がタッチデバイスかどうか\n * @returns タッチデバイスなら true を返す\n */\n static isTouchDevice(): boolean {\n return window.matchMedia('(hover: none)').matches;\n }\n\n\n /**\n * 任意の桁で切り捨てする\n * ref: https://qiita.com/nagito25/items/0293bc317067d9e6c560#comment-87f0855f388953843037\n * @param value 切り捨てする数値\n * @param base どの桁で切り捨てするか (-1 → 10の位 / 3 → 小数第3位)\n * @return 切り捨てした値\n */\n static mathFloor(value: number, base: number = 0): number {\n return Math.floor(value * (10**base)) / (10**base);\n }\n\n\n /**\n * async/await でスリープ的なもの\n * @param seconds 待機する秒数 (ミリ秒単位ではないので注意)\n * @returns Promise を返すので、await sleep(1); のように使う\n */\n static async sleep(seconds: number): Promise {\n return await new Promise(resolve => setTimeout(resolve, seconds * 1000));\n }\n\n\n /**\n * 現在時刻の UNIX タイムスタンプ (秒単位) を取得する (デバッグ用)\n * @returns 現在時刻の UNIX タイムスタンプ (秒単位)\n */\n static time(): number {\n return Date.now() / 1000;\n }\n\n\n /**\n * 指定された値の型の名前を取得する\n * ref: https://qiita.com/amamamaou/items/ef0b797156b324bb4ef3\n * @returns 指定された値の型の名前\n */\n static typeof(value: any): string {\n return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();\n }\n\n\n /**\n * 文字列中に含まれる URL をリンクの HTML に置き換える\n * @param text 置換対象の文字列\n * @returns URL をリンクに置換した文字列\n */\n static URLtoLink(text: string): string {\n\n // HTML の特殊文字で表示がバグらないように、事前に HTML エスケープしておく\n text = Utils.escapeHTML(text);\n\n // ref: https://www.softel.co.jp/blogs/tech/archives/6099\n const pattern = /(https?:\\/\\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/ig;\n return text.replace(pattern, '$1');\n }\n}\n","\nimport { ChannelType } from '@/services/Channels';\n\n\n/**\n * チャンネル周りのユーティリティ\n */\nexport class ChannelUtils {\n\n /**\n * チャンネル ID からチャンネルタイプを取得する\n * @param channel_id チャンネル ID\n * @returns チャンネルタイプ\n */\n static getChannelType(channel_id: string): ChannelType {\n try {\n const result = channel_id.match('(?[a-z]+)[0-9]+').groups.channel_type.toUpperCase();\n return result as ChannelType;\n } catch (e) {\n // 何かしらエラーが発生したということはチャンネル ID が不正\n // とりあえずここではエラーにならないよう GR を返す エラー処理はその後の段階で行われる\n return 'GR';\n }\n }\n\n\n /**\n * チャンネルの実況勢いから「多」「激多」「祭」を取得する\n * ref: https://ja.wikipedia.org/wiki/%E3%83%8B%E3%82%B3%E3%83%8B%E3%82%B3%E5%AE%9F%E6%B3%81\n * @param channel_force チャンネルの実況勢い\n * @returns normal(普通)or many(多)or so-many(激多)or festival(祭)\n */\n static getChannelForceType(channel_force: number | null): 'normal' | 'many' | 'so-many' | 'festival' {\n\n // 実況勢いが null(=対応する実況チャンネルがない)\n if (channel_force === null) return 'normal';\n\n // 実況勢いが 500 コメント以上(祭)\n if (channel_force >= 500) return 'festival';\n // 実況勢いが 200 コメント以上(激多)\n if (channel_force >= 200) return 'so-many';\n // 実況勢いが 100 コメント以上(多)\n if (channel_force >= 100) return 'many';\n\n // それ以外\n return 'normal';\n }\n}\n","\nimport { MessageOption, MessageType } from 'vuetify-message-snackbar/src/message';\n\n\ninterface MessageReturnValue {\n close(): void;\n again(): void;\n}\n\n// Vue コンポーネント以外からも this.$message を使えるようにするための (強引な) ラッパー\nexport default {\n success(message: MessageType | MessageOption): MessageReturnValue {\n // @ts-ignore\n return window.KonomiTVVueInstance?.$message.success(message);\n },\n info(message: MessageType | MessageOption): MessageReturnValue {\n // @ts-ignore\n return window.KonomiTVVueInstance?.$message.info(message);\n },\n warning(message: MessageType | MessageOption): MessageReturnValue {\n // @ts-ignore\n return window.KonomiTVVueInstance?.$message.warning(message);\n },\n error(message: MessageType | MessageOption): MessageReturnValue {\n // @ts-ignore\n return window.KonomiTVVueInstance?.$message.error(message);\n },\n show(message: MessageType | MessageOption): MessageReturnValue {\n // @ts-ignore\n return window.KonomiTVVueInstance?.$message.show(message);\n }\n};\n","\nimport axios from 'axios';\n\nimport Utils from '@/utils';\n\n\n// Axios のインスタンスを作成\nconst axios_instance = axios.create();\n\n// HTTP リクエスト前に割り込んで行われる処理\naxios_instance.interceptors.request.use((config) => {\n\n // API のベース URL を設定 (config.baseURL が指定されていない場合のみ)\n if (config.baseURL === undefined) {\n config.baseURL = Utils.api_base_url;\n }\n\n // 外部サイトへの HTTP リクエストでは実行しない\n if (config.url?.startsWith('http') === false) {\n\n // アクセストークンが取得できたら(=ログインされていれば)\n // 取得したアクセストークンを Authorization ヘッダーに Bearer トークンとしてセット\n // これを忘れると(当然ながら)ログインしていない扱いになる\n const access_token = Utils.getAccessToken();\n if (access_token !== null) {\n config.headers['Authorization'] = `Bearer ${access_token}`;\n }\n\n // KonomiTV クライアントのバージョンを設定\n // 今のところ使わないが、将来的にクライアントとサーバーを分離することを見据えて念のため\n config.headers['X-KonomiTV-Version'] = Utils.version;\n }\n\n // タイムアウト時間を30秒に設定\n config.timeout = 30 * 1000;\n\n return config;\n});\n\nexport default axios_instance;\n","\nimport Message from '@/message';\nimport APIClient from '@/services/APIClient';\n\n\n/** ユーザーアカウントの情報を表すインターフェイス */\nexport interface IUser {\n id: number;\n name: string;\n is_admin: boolean;\n niconico_user_id: number | null;\n niconico_user_name: string | null;\n niconico_user_premium: boolean | null;\n twitter_accounts: ITwitterAccount[];\n created_at: string;\n updated_at: string;\n}\n\n/** ユーザーアカウントに紐づく Twitter アカウントの情報を表すインターフェイス */\nexport interface ITwitterAccount {\n id: number;\n name: string;\n screen_name: string;\n icon_url: string;\n is_oauth_session: boolean;\n created_at: string;\n updated_at: string;\n}\n\n/** ユーザーアカウントのアクセストークンを表すインターフェイス */\nexport interface IUserAccessToken {\n access_token: string;\n token_type: string;\n}\n\nexport interface IUserCreateRequest {\n username: string;\n password: string;\n}\nexport interface IUserUpdateRequest {\n username?: string;\n password?: string;\n}\nexport interface IUserUpdateRequestForAdmin {\n username?: string;\n password?: string;\n is_admin?: boolean;\n}\n\n\nclass Users {\n\n /**\n * ユーザーアカウントを作成する\n * @param user_create_request ユーザー名とパスワード\n * @returns 作成したユーザーアカウントの情報 or アカウント作成に失敗した場合は null\n */\n static async createUser(user_create_request: IUserCreateRequest): Promise {\n\n // API リクエストを実行\n const response = await APIClient.post('/users', user_create_request);\n\n // エラー処理\n if ('is_error' in response) {\n switch (response.error.message) {\n case 'Specified username is duplicated': {\n Message.error('ユーザー名が重複しています。');\n break;\n }\n case 'Specified username is not accepted due to system limitations': {\n Message.error('ユーザー名に token と me は使えません。');\n break;\n }\n default: {\n APIClient.showGenericError(response, 'アカウントを作成できませんでした。');\n break;\n }\n }\n return null;\n }\n\n return response.data;\n }\n\n\n /**\n * ユーザーアカウントのアクセストークンを発行する\n * @param username ユーザー名\n * @param password パスワード\n * @returns 発行したアクセストークン or ログインに失敗した場合は null\n */\n static async createUserAccessToken(username: string, password: string): Promise {\n\n // API リクエストを実行\n const response = await APIClient.post('/users/token', new URLSearchParams({username, password}));\n\n // エラー処理\n if ('is_error' in response) {\n switch (response.error.message) {\n case 'Incorrect username': {\n Message.error('ログインできませんでした。そのユーザー名のアカウントは存在しません。');\n break;\n }\n case 'Incorrect password': {\n Message.error('ログインできませんでした。パスワードを間違えていませんか?');\n break;\n }\n default: {\n APIClient.showGenericError(response, 'ログインできませんでした。');\n break;\n }\n }\n return null;\n }\n\n return response.data;\n }\n\n\n /**\n * 現在ログイン中のユーザーアカウントの情報を取得する\n * @returns ログイン中のユーザーアカウントの情報 or ログインしていない場合は null\n */\n static async fetchUser(): Promise {\n\n // API リクエストを実行\n const response = await APIClient.get('/users/me');\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'アカウント情報を取得できませんでした。');\n return null;\n }\n\n return response.data;\n }\n\n\n /**\n * 現在ログイン中のユーザーアカウントのアイコンを取得する\n * @returns ログイン中のユーザーアカウントのアイコンの Blob URL or ログインしていない場合は null\n */\n static async fetchUserIcon(): Promise {\n\n // API リクエストを実行\n const response = await APIClient.get('/users/me/icon', {responseType: 'blob'});\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'アイコン画像を取得できませんでした。');\n return null;\n }\n\n return URL.createObjectURL(response.data);\n }\n\n\n /**\n * 現在ログイン中のユーザーアカウントの情報を更新する\n * @param user_update_request ユーザー名 or パスワード\n */\n static async updateUser(user_update_request: IUserUpdateRequest): Promise {\n\n // API リクエストを実行\n const response = await APIClient.put('/users/me', user_update_request);\n\n // エラー処理\n if ('is_error' in response) {\n switch (response.error.message) {\n case 'Specified username is duplicated': {\n Message.error('ユーザー名が重複しています。');\n break;\n }\n case 'Specified username is not accepted due to system limitations': {\n Message.error('ユーザー名に token と me は使えません。');\n break;\n }\n default: {\n APIClient.showGenericError(response, 'アカウント情報を更新できませんでした。');\n break;\n }\n }\n return;\n }\n }\n\n\n /**\n * 現在ログイン中のユーザーアカウントのアイコン画像を更新する\n * @param icon アイコンの File オブジェクト\n */\n static async updateUserIcon(icon: File): Promise {\n\n // アイコン画像の File オブジェクト (= Blob) を FormData に入れる\n // multipart/form-data で送るために必要\n // ref: https://r17n.page/2020/02/04/nodejs-axios-file-upload-api/\n const form_data = new FormData();\n form_data.append('image', icon);\n\n // API リクエストを実行\n const response = await APIClient.put('/users/me/icon', form_data, {headers: {'Content-Type': 'multipart/form-data'}});\n\n // エラー処理\n if ('is_error' in response) {\n switch (response.error.message) {\n case 'Please upload JPEG or PNG image': {\n Message.error('JPEG または PNG 画像をアップロードしてください。');\n break;\n }\n default: {\n APIClient.showGenericError(response, 'アイコン画像を更新できませんでした。');\n break;\n }\n }\n return;\n }\n }\n\n\n /**\n * 現在ログイン中のユーザーアカウントを削除する\n */\n static async deleteUser(): Promise {\n\n // API リクエストを実行\n const response = await APIClient.delete('/users/me');\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'アカウントを削除できませんでした。');\n return;\n }\n }\n}\n\nexport default Users;\n","\nimport { defineStore } from 'pinia';\n\nimport Message from '@/message';\nimport Users, { IUser, IUserUpdateRequest } from '@/services/Users';\nimport useSettingsStore from '@/store/SettingsStore';\nimport Utils from '@/utils';\n\n\n/**\n * 現在ログイン中のユーザーアカウントの情報を共有するストア\n */\nconst useUserStore = defineStore('user', {\n state: () => ({\n\n // 現在ログイン中かどうか\n is_logged_in: false as boolean,\n\n // ログイン済みのユーザーの情報\n user: null as IUser | null,\n\n // ログイン済みのユーザーのアイコン画像の Blob URL\n user_icon_url: null as string | null,\n }),\n getters: {\n\n /**\n * ログイン済みのユーザーのニコニコアカウントのユーザーアイコンの URL (ニコニコアカウントと連携されている場合のみ)\n */\n user_niconico_icon_url(): string | null {\n if (this.user === null || this.user.niconico_user_id === null) {\n return null;\n }\n const user_id_slice = this.user.niconico_user_id.toString().slice(0, 4);\n return `https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/${user_id_slice}/${this.user.niconico_user_id}.jpg`;\n }\n },\n actions: {\n\n /**\n * アカウントを作成する\n * @param username ユーザー名\n * @param password パスワード\n * @returns アカウント作成に成功した場合は true\n */\n async register(username: string, password: string): Promise {\n\n // アカウントを作成\n const result = await Users.createUser({username, password});\n if (result === null) {\n console.log('Register failed.');\n return false; // アカウント作成失敗 (エラーハンドリングは services 層で行われる)\n }\n\n // 作成したアカウントでログイン\n await this.login(username, password, true);\n console.log('Register successful.');\n Message.success('アカウントを作成しました。');\n return true;\n },\n\n /**\n * ログイン処理を行う\n * @param username ユーザー名\n * @param password パスワード\n * @param silent ログインしたことをメッセージで通知しない場合は true\n * @returns ログインに成功した場合は true\n */\n async login(username: string, password: string, silent: boolean = false): Promise {\n\n // アクセストークンを発行\n const access_token = await Users.createUserAccessToken(username, password);\n if (access_token === null) {\n console.log('Login failed.');\n this.logout(true);\n return false; // ログイン失敗 (エラーハンドリングは services 層で行われる)\n }\n\n // 取得したアクセストークンを保存\n console.log('Login successful.');\n Utils.saveAccessToken(access_token.access_token);\n this.is_logged_in = true;\n\n // ユーザーアカウントの情報を取得\n await this.fetchUser(true);\n\n if (silent === false) {\n Message.success('ログインしました。');\n }\n\n return true;\n },\n\n /**\n * ログアウト処理を行う\n * @param silent ログアウトしたことをメッセージで通知しない場合は true\n */\n logout(silent: boolean = false): void {\n\n // 設定の同期を無効化\n const settings_store = useSettingsStore();\n settings_store.settings.sync_settings = false;\n\n // ブラウザからアクセストークンを削除\n // これをもってログアウトしたことになる(それ以降の Axios のリクエストにはアクセストークンが含まれなくなる)\n Utils.deleteAccessToken();\n\n // 未ログイン状態に設定\n this.is_logged_in = false;\n this.user = null;\n this.user_icon_url = '';\n\n if (silent === false) {\n Message.success('ログアウトしました。');\n }\n },\n\n /**\n * 現在ログイン中のユーザーアカウントの情報を取得する\n * すでに取得済みの情報がある場合は API リクエストを行わずにそれを返す\n * @param force 強制的に API リクエストを行う場合は true\n * @returns ログイン中のユーザーアカウントの情報 or ログインしていない場合は null\n */\n async fetchUser(force: boolean = false): Promise {\n\n // LocalStorage にアクセストークンが保存されていない場合 (= 非ログイン状態) は常に null を返す\n if (Utils.getAccessToken() === null) {\n return null;\n }\n\n // すでにログイン済みのユーザーアカウントの情報がある場合はそれを返す\n // force が true の場合は無視される\n if (this.user !== null && force === false) {\n return this.user;\n }\n\n // ユーザーアカウントの情報を取得する\n const user = await Users.fetchUser();\n if (user === null) {\n // この時点で無効などの理由でアクセストークンが削除されている場合、ログアウトする\n if (Utils.getAccessToken() === null) {\n this.logout(true);\n }\n return null;\n }\n this.is_logged_in = true;\n this.user = user;\n\n // ユーザーアカウントのアイコン画像の Blob URL を取得する\n const user_icon_url = await Users.fetchUserIcon();\n if (user_icon_url === null) {\n return null;\n }\n this.user_icon_url = user_icon_url;\n\n return this.user;\n },\n\n /**\n * 現在ログイン中のユーザーアカウントの情報を更新する\n * @param user_update_request ユーザー名 or パスワード\n */\n async updateUser(user_update_request: IUserUpdateRequest): Promise {\n\n // ユーザーアカウントの情報を更新する\n await Users.updateUser(user_update_request);\n\n // ユーザーアカウントの情報を再取得する\n await this.fetchUser(true);\n\n if (user_update_request.username !== undefined) {\n Message.show('ユーザー名を更新しました。');\n } else if (user_update_request.password !== undefined) {\n Message.show('パスワードを更新しました。');\n }\n },\n\n /**\n * 現在ログイン中のユーザーアカウントのアイコン画像を更新する\n * @param icon アイコンの File オブジェクト\n */\n async updateUserIcon(icon: File): Promise {\n\n // ユーザーアカウントのアイコン画像を更新する\n await Users.updateUserIcon(icon);\n\n // ユーザーアカウントの情報を再取得する\n await this.fetchUser(true);\n\n Message.show('アイコン画像を更新しました。');\n },\n\n /**\n * 現在ログイン中のユーザーアカウントを削除する\n */\n async deleteUser(): Promise {\n\n // ユーザーアカウントを削除する\n await Users.deleteUser();\n\n // ログアウトする\n this.logout(true);\n\n Message.show('アカウントを削除しました。');\n }\n }\n});\n\nexport default useUserStore;\n","\n/**\n * services/ 以下の各クラスは、KonomiTV サーバーへの API リクエストを抽象化し、\n * API レスポンスの受け取りと、エラーが発生した際のエラーハンドリング (エラーメッセージ表示) までを責務として負う\n */\n\nimport { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';\n\nimport Message from '@/message';\nimport axios from '@/plugins/axios';\nimport useUserStore from '@/store/UserStore';\n\n\n/** API のエラーレスポンスを表すインターフェイス */\nexport interface IError {\n detail: string;\n}\n\n/** API リクエスト成功時のレスポンスを表すインターフェイス */\nexport type SuccessResponse = {\n status: number;\n data: T;\n error: null;\n is_success: true;\n};\n\n/** API リクエスト失敗時のレスポンスを表すインターフェイス */\nexport type ErrorResponse = {\n status: number;\n data: null;\n error: T;\n is_error: true;\n};\n\n\n/**\n * services/ 以下の各クラスから呼び出される、Axios の薄いラッパー\n * エラーハンドリングを容易にするために、レスポンスを SuccessResponse と ErrorResponse に分けて返す\n * ref: https://zenn.dev/engineer_titan/articles/291c9fccb338e2\n */\nclass APIClient {\n\n /**\n * Axios で HTTP リクエストを送信し、レスポンスを受け取る\n * @param request AxiosRequestConfig\n * @returns 成功なら SuccessResponse 、失敗なら ErrorResponse を返す\n */\n static async request(request: AxiosRequestConfig): Promise | ErrorResponse> {\n\n // Axios で HTTP リクエストを送信し、レスポンスを受け取る\n const result: AxiosResponse | AxiosError = await axios.request(request).catch((error: AxiosError) => error);\n\n // エラーが発生した場合は ErrorResponse を返す\n if (result instanceof AxiosError) {\n console.error(result);\n\n // エラーレスポンスがあれば、エラー内容を取得して返す\n if (result.response) {\n return {\n status: result.response.status,\n data: null,\n error: new Error(result.response.data.detail),\n is_error: true,\n };\n\n // エラーレスポンスがない場合は、AxiosError をそのまま返す\n } else {\n return {\n status: NaN,\n data: null,\n error: result,\n is_error: true,\n };\n }\n\n // 正常にレスポンスが返ってきた場合は SuccessResponse を返す\n } else {\n return {\n status: result.status,\n data: result.data,\n error: null,\n is_success: true,\n };\n }\n }\n\n\n /**\n * GET リクエストを送信する\n * @param url リクエスト先の URL\n * @returns 成功なら SuccessResponse 、失敗なら ErrorResponse を返す\n */\n static async get(url: string, config?: AxiosRequestConfig): Promise | ErrorResponse> {\n const request: AxiosRequestConfig = {\n url: url,\n method: 'GET',\n ...config,\n };\n return await APIClient.request(request);\n }\n\n\n /**\n * POST リクエストを送信する\n * @param url リクエスト先の URL\n * @returns 成功なら SuccessResponse 、失敗なら ErrorResponse を返す\n */\n static async post(url: string, data?: D, config?: AxiosRequestConfig): Promise | ErrorResponse> {\n const request: AxiosRequestConfig = {\n url: url,\n method: 'POST',\n data: data,\n ...config,\n };\n return await APIClient.request(request);\n }\n\n\n /**\n * PUT リクエストを送信する\n * @param url リクエスト先の URL\n * @returns 成功なら SuccessResponse 、失敗なら ErrorResponse を返す\n */\n static async put(url: string, data?: D, config?: AxiosRequestConfig): Promise | ErrorResponse> {\n const request: AxiosRequestConfig = {\n url: url,\n method: 'PUT',\n data: data,\n ...config,\n };\n return await APIClient.request(request);\n }\n\n\n /**\n * DELETE リクエストを送信する\n * @param url リクエスト先の URL\n * @returns 成功なら SuccessResponse 、失敗なら ErrorResponse を返す\n */\n static async delete(url: string, config?: AxiosRequestConfig): Promise | ErrorResponse> {\n const request: AxiosRequestConfig = {\n url: url,\n method: 'DELETE',\n ...config,\n };\n return await APIClient.request(request);\n }\n\n\n /**\n * 一般的なエラーメッセージの共通処理\n * エラーメッセージを SnackBar で表示する\n * @param response API から返されたエラーレスポンス\n * @param template エラーメッセージのテンプレート(「アカウント情報を取得できませんでした。」など)\n */\n static showGenericError(response: ErrorResponse, template: string): void {\n const user_store = useUserStore();\n switch (response.error.message) {\n case 'Not authenticated': {\n user_store.logout(true);\n Message.error(`${template}\\nログインし直してください。`);\n return;\n }\n case 'Access token data is invalid': {\n user_store.logout(true);\n Message.error(`${template}\\nログインセッションが不正です。もう一度ログインし直してください。`);\n return;\n }\n case 'Access token is invalid': {\n user_store.logout(true);\n Message.error(`${template}\\nログインセッションの有効期限が切れています。もう一度ログインし直してください。`);\n return;\n }\n case 'User associated with access token does not exist': {\n user_store.logout(true);\n Message.error(`${template}\\nログインセッションに紐づくユーザーが存在しないか、削除されています。`);\n return;\n }\n default: {\n if (response.error.message) {\n if (Number.isNaN(response.status)) {\n Message.error(`${template}(${response.error.message})`);\n } else {\n Message.error(`${template}(HTTP Error ${response.status} / ${response.error.message})`);\n }\n } else {\n Message.error(`${template}(HTTP Error ${response.status})`);\n }\n return;\n }\n }\n }\n}\n\nexport default APIClient;\n","\nimport APIClient from '@/services/APIClient';\n\n\n/**\n * サーバーに保存されるクライアント設定を表すインターフェース\n * サーバー側の app.schemas.ClientSettings と\n * client/src/store/SettingsStore.ts 内の sync_settings_keys で定義されているものと同じ\n */\nexport interface IClientSettings {\n pinned_channel_ids: string[];\n // showed_panel_last_time: 同期無効\n // selected_twitter_account_id: 同期無効\n saved_twitter_hashtags: string[];\n // tv_streaming_quality: 同期無効\n // tv_data_saver_mode: 同期無効\n // tv_low_latency_mode: 同期無効\n panel_display_state: 'RestorePreviousState' | 'AlwaysDisplay' | 'AlwaysFold';\n tv_panel_active_tab: 'Program' | 'Channel' | 'Comment' | 'Twitter';\n tv_channel_selection_requires_alt_key: boolean;\n caption_font: string;\n always_border_caption_text: boolean;\n specify_caption_opacity: boolean;\n caption_opacity: number;\n tv_show_superimpose: boolean;\n // capture_copy_to_clipboard: 同期無効\n capture_save_mode: 'Browser' | 'UploadServer' | 'Both';\n capture_caption_mode: 'VideoOnly' | 'CompositingCaption' | 'Both';\n // sync_settings: 同期無効\n comment_speed_rate: number;\n comment_font_size: number;\n close_comment_form_after_sending: boolean;\n muted_comment_keywords: IMutedCommentKeywords[];\n muted_niconico_user_ids: string[];\n mute_vulgar_comments: boolean;\n mute_abusive_discriminatory_prejudiced_comments: boolean;\n mute_big_size_comments: boolean;\n mute_fixed_comments: boolean;\n mute_colored_comments: boolean;\n mute_consecutive_same_characters_comments: boolean;\n fold_panel_after_sending_tweet: boolean;\n reset_hashtag_when_program_switches: boolean;\n auto_add_watching_channel_hashtag: boolean;\n twitter_active_tab: 'Search' | 'Timeline' | 'Capture';\n tweet_hashtag_position: 'Prepend' | 'Append' | 'PrependWithLineBreak' | 'AppendWithLineBreak';\n tweet_capture_watermark_position: 'None' | 'TopLeft' | 'TopRight' | 'BottomLeft' | 'BottomRight';\n}\n\n/**\n * ミュート対象のコメントのキーワードのインターフェイス\n */\nexport interface IMutedCommentKeywords {\n match: 'partial' | 'forward' | 'backward' | 'exact' | 'regex';\n pattern: string;\n}\n\n\nclass Settings {\n\n /**\n * クライアント設定を取得する\n * @return クライアント設定 (取得に失敗した場合は null)\n */\n static async fetchClientSettings(): Promise {\n\n // API リクエストを実行\n const response = await APIClient.get('/settings/client');\n\n // エラー処理 (基本起こらないはず & 実行できなくても後続の処理に影響しないため何もしない)\n if ('is_error' in response) {\n return null;\n }\n\n return response.data;\n }\n\n\n /**\n * クライアント設定を更新する\n * @param settings クライアント設定\n */\n static async updateClientSettings(settings: IClientSettings): Promise {\n\n // API リクエストを実行\n // 正常時は 204 No Content が返るし、エラーは基本起こらないはずなので何もしない\n await APIClient.put('/settings/client', settings);\n }\n}\n\nexport default Settings;\n","\nimport { defineStore } from 'pinia';\n\nimport Settings, { IClientSettings, IMutedCommentKeywords } from '@/services/Settings';\nimport Utils from '@/utils';\n\n\n// LocalStorage に保存される KonomiTV の設定データ\ninterface ILocalClientSettings {\n pinned_channel_ids: string[];\n showed_panel_last_time: boolean;\n selected_twitter_account_id: number | null;\n saved_twitter_hashtags: string[];\n tv_streaming_quality: '1080p-60fps' | '1080p' | '810p' | '720p' | '540p' | '480p' | '360p' | '240p';\n tv_data_saver_mode: boolean;\n tv_low_latency_mode: boolean;\n panel_display_state: 'RestorePreviousState' | 'AlwaysDisplay' | 'AlwaysFold';\n tv_panel_active_tab: 'Program' | 'Channel' | 'Comment' | 'Twitter';\n tv_channel_selection_requires_alt_key: boolean;\n caption_font: string;\n always_border_caption_text: boolean;\n specify_caption_opacity: boolean;\n caption_opacity: number;\n tv_show_superimpose: boolean;\n capture_copy_to_clipboard: boolean;\n capture_save_mode: 'Browser' | 'UploadServer' | 'Both';\n capture_caption_mode: 'VideoOnly' | 'CompositingCaption' | 'Both';\n sync_settings: boolean;\n comment_speed_rate: number;\n comment_font_size: number;\n close_comment_form_after_sending: boolean;\n muted_comment_keywords: IMutedCommentKeywords[];\n muted_niconico_user_ids: string[];\n mute_vulgar_comments: boolean;\n mute_abusive_discriminatory_prejudiced_comments: boolean;\n mute_big_size_comments: boolean;\n mute_fixed_comments: boolean;\n mute_colored_comments: boolean;\n mute_consecutive_same_characters_comments: boolean;\n fold_panel_after_sending_tweet: boolean;\n reset_hashtag_when_program_switches: boolean;\n auto_add_watching_channel_hashtag: boolean;\n twitter_active_tab: 'Search' | 'Timeline' | 'Capture';\n tweet_hashtag_position: 'Prepend' | 'Append' | 'PrependWithLineBreak' | 'AppendWithLineBreak';\n tweet_capture_watermark_position: 'None' | 'TopLeft' | 'TopRight' | 'BottomLeft' | 'BottomRight';\n}\n\n// 設定データのうち、同期対象の設定キー\n// サーバー側の app.schemas.ClientSettings と\n// client/src/services/Settings.ts 内の IClientSettings で定義されているものと同じ\nconst sync_settings_keys = [\n 'pinned_channel_ids',\n // showed_panel_last_time: 同期無効\n // selected_twitter_account_id: 同期無効\n 'saved_twitter_hashtags',\n // tv_streaming_quality: 同期無効\n // tv_data_saver_mode: 同期無効\n // tv_low_latency_mode: 同期無効\n 'panel_display_state',\n 'tv_panel_active_tab',\n 'tv_channel_selection_requires_alt_key',\n 'caption_font',\n 'always_border_caption_text',\n 'specify_caption_opacity',\n 'caption_opacity',\n 'tv_show_superimpose',\n // capture_copy_to_clipboard: 同期無効\n 'capture_save_mode',\n 'capture_caption_mode',\n // sync_settings: 同期無効\n 'comment_speed_rate',\n 'comment_font_size',\n 'close_comment_form_after_sending',\n 'muted_comment_keywords',\n 'muted_niconico_user_ids',\n 'mute_vulgar_comments',\n 'mute_abusive_discriminatory_prejudiced_comments',\n 'mute_big_size_comments',\n 'mute_fixed_comments',\n 'mute_colored_comments',\n 'mute_consecutive_same_characters_comments',\n 'fold_panel_after_sending_tweet',\n 'reset_hashtag_when_program_switches',\n 'auto_add_watching_channel_hashtag',\n 'twitter_active_tab',\n 'tweet_hashtag_position',\n 'tweet_capture_watermark_position',\n];\n\n// LocalStorage に保存される KonomiTV の設定データのデフォルト値\nconst default_settings: ILocalClientSettings = {\n\n // ***** 設定画面から直接変更できない設定値 *****\n\n // ピン留めしているチャンネルの ID (ex: gr011) が入るリスト\n pinned_channel_ids: [],\n // 前回視聴画面を開いた際にパネルが表示されていたかどうか (同期無効)\n showed_panel_last_time: true,\n // 現在ツイート対象として選択されている Twitter アカウントの ID (同期無効)\n selected_twitter_account_id: null,\n // 保存している Twitter のハッシュタグが入るリスト\n saved_twitter_hashtags: [],\n\n // ***** 設定 → 全般 *****\n\n // テレビのデフォルトのストリーミング画質 (Default: 1080p) (同期無効)\n tv_streaming_quality: '1080p',\n // テレビを通信節約モードで視聴する (Default: オフ) (同期無効)\n tv_data_saver_mode: false,\n // テレビを低遅延で視聴する (Default: 低遅延で視聴する) (同期無効)\n tv_low_latency_mode: true,\n // デフォルトのパネルの表示状態 (Default: 前回の状態を復元する)\n panel_display_state: 'RestorePreviousState',\n // テレビをみるときにデフォルトで表示されるパネルのタブ (Default: 番組情報タブ)\n tv_panel_active_tab: 'Program',\n // チャンネル選局のキーボードショートカットを Alt or Option + 数字キー/テンキーに変更する (Default: オフ)\n tv_channel_selection_requires_alt_key: false,\n\n // ***** 設定 → 字幕 *****\n\n // 字幕のフォント (Default: Windows TV 丸ゴシック)\n caption_font: 'Windows TV MaruGothic',\n // 字幕の文字を常に縁取って描画する (Default: 常に縁取る)\n always_border_caption_text: true,\n // 字幕の不透明度を指定する (Default: 指定しない)\n specify_caption_opacity: false,\n // 字幕の不透明度 (Default: 50%)\n caption_opacity: 0.5,\n // テレビをみるときに文字スーパーを表示する (Default: 表示する)\n tv_show_superimpose: true,\n\n // ***** 設定 → キャプチャ *****\n\n // キャプチャをクリップボードにコピーする (Default: 無効) (同期無効)\n capture_copy_to_clipboard: false,\n // キャプチャの保存先 (Default: KonomiTV サーバーにアップロード)\n capture_save_mode: 'UploadServer',\n // 字幕が表示されているときのキャプチャの保存モード (Default: 映像のみのキャプチャと、字幕を合成したキャプチャを両方保存する)\n capture_caption_mode: 'Both',\n\n // ***** 設定 → アカウント *****\n\n // 設定を同期する (Default: 同期しない) (同期無効)\n sync_settings: false,\n\n // ***** 設定 → ニコニコ実況 *****\n\n // コメントの速さ (Default: 1倍)\n comment_speed_rate: 1,\n // コメントのフォントサイズ (Default: 34px)\n comment_font_size: 34,\n // コメント送信後にコメント入力フォームを閉じる (Default: オン)\n close_comment_form_after_sending: true,\n\n // ***** 設定 → ニコニコ実況 (ミュート設定) *****\n\n // ミュート済みのコメントのキーワードが入るリスト\n muted_comment_keywords: [],\n // ミュート済みのニコニコユーザー ID が入るリスト\n muted_niconico_user_ids: [],\n // 露骨な表現を含むコメントをミュートする (Default: ミュートする)\n mute_vulgar_comments: true,\n // 罵倒や誹謗中傷、差別的な表現、政治的に偏った表現を含むコメントをミュートする (Default: ミュートする)\n mute_abusive_discriminatory_prejudiced_comments: true,\n // 文字サイズが大きいコメントをミュートする (Default: ミュートする)\n mute_big_size_comments: true,\n // 映像の上下に固定表示されるコメントをミュートする (Default: ミュートしない)\n mute_fixed_comments: false,\n // 色付きのコメントをミュートする (Default: ミュートしない)\n mute_colored_comments: false,\n // 8文字以上同じ文字が連続しているコメントをミュートする (Default: ミュートしない)\n mute_consecutive_same_characters_comments: false,\n\n // ***** 設定 → Twitter *****\n\n // ツイート送信後にパネルを折りたたむ (Default: オフ)\n fold_panel_after_sending_tweet: false,\n // 番組が切り替わったときにハッシュタグフォームをリセットする (Default: オン)\n reset_hashtag_when_program_switches: true,\n // 視聴中のチャンネルに対応する局タグを自動で追加する (Default: オン)\n auto_add_watching_channel_hashtag: true,\n // デフォルトで表示される Twitter タブ内のタブ (Default: キャプチャタブ)\n twitter_active_tab: 'Capture',\n // ツイートにつけるハッシュタグの位置 (Default: ツイート本文の後に追加する)\n tweet_hashtag_position: 'Append',\n // ツイートするキャプチャに番組名の透かしを描画する (Default: 透かしを描画しない)\n tweet_capture_watermark_position: 'None',\n};\n\n/**\n * LocalStorage の KonomiTV-Settings キーから設定データを取得する\n * @returns 設定データ\n */\nexport function getLocalStorageSettings(): {[key: string]: any} {\n const settings = localStorage.getItem('KonomiTV-Settings');\n if (settings !== null) {\n return JSON.parse(settings);\n } else {\n // もし LocalStorage に KonomiTV-Settings キーがまだない場合、あらかじめデフォルトの設定値を保存しておく\n setLocalStorageSettings(default_settings);\n return default_settings;\n }\n}\n\n/**\n * LocalStorage の KonomiTV-Settings キーに設定データを保存する\n * @param settings 設定データ\n */\nexport function setLocalStorageSettings(settings: {[key: string]: any}): void {\n localStorage.setItem('KonomiTV-Settings', JSON.stringify(settings));\n}\n\n/**\n * 与えられた設定データを並び替えたり足りない設定キーを補完したり不要な設定キーを削除したりと整形して返す\n * @param settings 設定データ\n */\nfunction getNormalizedSettings(settings: {[key: string]: any}): ILocalClientSettings {\n\n // (名前が変わった、廃止されたなどの理由で) 現在の default_settings に存在しない設定キーを排除した上で並び替え\n // 並び替えられていないと設定データの比較がうまくいかない\n const new_settings: {[key: string]: any} = {};\n for (const default_settings_key of Object.keys(default_settings)) {\n if (default_settings_key in settings) {\n new_settings[default_settings_key] = settings[default_settings_key];\n } else {\n // 後のバージョンで追加されたなどの理由で現状の KonomiTV-Settings に存在しない設定キーの場合\n // その設定キーのデフォルト値を取得する\n new_settings[default_settings_key] = default_settings[default_settings_key];\n }\n }\n\n // この状態の新しい設定データを返す\n return new_settings as ILocalClientSettings;\n}\n\n/**\n * 設定データを共有するストア\n */\nconst useSettingsStore = defineStore('settings', {\n state: () => {\n\n // ref: https://www.vuemastery.com/blog/refresh-proof-your-pinia-stores/\n\n // LocalStorage から設定データを取得する\n const settings = getLocalStorageSettings();\n\n // (名前が変わった、廃止されたなどの理由で) 現在の default_settings に存在しない設定キーを排除した上で並び替え\n const new_settings = getNormalizedSettings(settings);\n\n // この状態の新しい設定データを LocalStorage に保存する\n setLocalStorageSettings(new_settings);\n\n // 設定データを Store の state のデフォルト値として返す\n return {\n settings: new_settings as ILocalClientSettings,\n };\n },\n actions: {\n\n /**\n * エクスポートした JSON ファイルから設定データをインポートする (既存の設定はすべて上書きされる)\n * @param file エクスポートした JSON ファイル\n * @returns インポートに成功したかどうか\n */\n async importClientSettings(file: File): Promise {\n\n // JSON ファイルを読み込む\n const settings_json = await file.text();\n\n // JSON ファイルをパースする\n let settings = {};\n try {\n settings = JSON.parse(settings_json);\n } catch (error) {\n return false;\n }\n\n // (名前が変わった、廃止されたなどの理由で) 現在の default_settings に存在しない設定キーを排除した上で並び替え\n const new_settings = getNormalizedSettings(settings);\n\n // この状態の新しい設定データを LocalStorage に保存し、Store の state に反映する\n // このとき、既存の設定データはすべて上書きされる\n setLocalStorageSettings(new_settings);\n this.settings = new_settings;\n\n // 設定データをサーバーに同期する\n await this.syncClientSettingsToServer();\n\n return true;\n },\n\n /**\n * 設定データを初期状態にリセットする\n */\n async resetClientSettings(): Promise {\n\n // デフォルトの設定に現在設定の同期がオンになっているかだけ反映した設定データ\n const default_settings_modified: ILocalClientSettings = {\n ...default_settings,\n sync_settings: this.settings.sync_settings,\n };\n\n // デフォルト値の設定データを LocalStorage に保存し、Store の state に反映する\n setLocalStorageSettings(default_settings_modified);\n this.settings = default_settings_modified;\n\n // 設定データをサーバーに同期する\n await this.syncClientSettingsToServer();\n },\n\n /**\n * 設定データのうち、サーバーへの同期対象の設定キーのみで構成されたオブジェクト (IClientSettings と一致する) を返す\n * @returns サーバーへの同期対象の設定キーのみで構成されたオブジェクト\n */\n getSyncableClientSettings(): IClientSettings {\n\n // 同期対象の設定キーのみで設定データをまとめ直す\n // sync_settings には同期対象外の設定は含まれない\n const sync_settings: {[key: string]: any} = {};\n for (const sync_settings_key of sync_settings_keys) {\n if (sync_settings_key in this.settings) {\n sync_settings[sync_settings_key] = this.settings[sync_settings_key];\n } else {\n // 後から追加された設定キーなどの理由で設定キーが現状の KonomiTV-Settings に存在しない場合\n // その設定キーのデフォルト値を取得する\n sync_settings[sync_settings_key] = default_settings[sync_settings_key];\n }\n }\n\n return sync_settings as IClientSettings;\n },\n\n /**\n * ログイン時かつ同期が有効な場合、サーバーに保存されている設定データをこのクライアントに同期する\n * @param force ログイン中なら同期が有効かに関わらず実行する (デフォルト: false)\n */\n async syncClientSettingsFromServer(force: boolean = false): Promise {\n\n // ログインしていない時、同期が無効なときは実行しない\n if (Utils.getAccessToken() === null || (this.settings.sync_settings === false && force === false)) {\n return;\n }\n\n // サーバーから設定データをダウンロード\n const settings_server = await Settings.fetchClientSettings();\n if (settings_server === null) {\n return; // 取得できなくても後続の処理には影響しないので、サイレントに失敗する\n }\n\n // クライアントの設定データをサーバーからの設定データで上書き\n for (const [settings_server_key, settings_server_value] of Object.entries(settings_server)) {\n this.settings[settings_server_key] = settings_server_value;\n }\n },\n\n /**\n * ログイン時かつ同期が有効な場合、このクライアントの設定をサーバーに同期する\n * @param force ログイン中なら同期が有効かに関わらず実行する (デフォルト: false)\n */\n async syncClientSettingsToServer(force: boolean = false): Promise {\n\n // ログインしていない時、同期が無効なときは実行しない\n if (Utils.getAccessToken() === null || (this.settings.sync_settings === false && force === false)) {\n return;\n }\n\n // 同期対象の設定キーのみで設定データをまとめ直す\n const sync_settings = this.getSyncableClientSettings();\n\n // サーバーに設定データをアップロード\n await Settings.updateClientSettings(sync_settings);\n }\n }\n});\n\nexport default useSettingsStore;\n","\nimport { Buffer } from 'buffer';\n\nimport useSettingsStore from '@/store/SettingsStore';\n\n\n/**\n * コメント周りのユーティリティ\n */\nexport class CommentUtils {\n\n // 「露骨な表現を含むコメントをミュートする」のフィルタ正規表現\n static readonly mute_vulgar_comments_pattern = new RegExp(Buffer.from('XChpXCl8XChVXCl8cHJwcnzvvZDvvZLvvZDvvZJ8U0VYfFPjgIdYfFPil69YfFPil4tYfFPil49YfO+8s++8pe+8uHzvvLPjgIfvvLh877yz4pev77y4fO+8s+KXi++8uHzvvLPil4/vvLh844Ki44OA44Or44OIfOOCouODiuOCpXzjgqLjg4rjg6t844Kk44Kr6IetfOOCpOOBj3zjgYbjgpPjgZN844Km44Oz44KzfOOBhuOCk+OBoXzjgqbjg7Pjg4F844Ko44Kt44ObfOOBiOOBoeOBiOOBoXzjgYjjgaPjgaF844Ko44OD44OBfOOBiOOBo+OCjXzjgqjjg4Pjg61844GI44KNfOOCqOODrXzlt6Xlj6N844GK44GV44KP44KK44G+44KTfOOBiuOBl+OBo+OBk3zjgqrjgrfjg4PjgrN844Kq44OD44K144OzfOOBiuOBo+OBseOBhHzjgqrjg4Pjg5HjgqR844Kq44OK44OL44O8fOOBiuOBquOBu3zjgqrjg4rjg5t844GK44Gx44GEfOOCquODkeOCpHzjgYpwfOOBiu+9kHzjgqrjg5Xjg5HjgrN844Ks44Kk44K444OzfOOCreODs+OCv+ODnnzjgY/jgbHjgYJ844GP44Gx44GBfOOCr+ODquODiOODquOCuXzjgq/jg7Pjg4t844GU44GP44GU44GP44GU44GP44GU44GPfOOCs+ODs+ODieODvOODoHzjgZHjgaTjgYLjgap844Kx44OE44Ki44OKfOOCtuODvOODoeODs3zjgrfjgrN844GX44GT44GX44GTfOOCt+OCs+OCt+OCs3zjgZnjgZHjgZnjgZF844Gb44GE44GI44GNfOOBm+OBhOOCinzjgZvjg7zjgop844GZ44GF44GF44GF44GF44GFfOOBmeOBhuOBhuOBhuOBhuOBhnzjgrvjgq/jg63jgrl844K744OD44Kv44K5fOOCu+ODleODrHzjgaHjgaPjgbHjgYR844Gh44Gj44OR44KkfOODgeODg+ODkeOCpHzjgaHjgpPjgZN844Gh44CH44GTfOOBoeKXr+OBk3zjgaHil4vjgZN844Gh4peP44GTfOODgeODs+OCs3zjg4HjgIfjgrN844OB4pev44KzfOODgeKXi+OCs3zjg4Hil4/jgrN844Gh44KT44G9fOOBoeOAh+OBvXzjgaHil6/jgb1844Gh4peL44G9fOOBoeKXj+OBvXzjg4Hjg7Pjg51844OB44CH44OdfOODgeKXr+ODnXzjg4Hil4vjg51844OB4peP44OdfOOBoeOCk+OBoeOCk3zjg4Hjg7Pjg4Hjg7N844Gm44GD44KT44Gm44GD44KTfOODhuOCo+ODs+ODhuOCo+ODs3zjg4bjgqPjg7Pjg51844OH44Kr44GEfOODh+ODquODmOODq3zjgarjgYvjgaDjgZd844Gq44GL44CH44GXfOOBquOBi+KXr+OBl3zjgarjgYvil4vjgZd844Gq44GL4peP44GXfOiEseOBknzjg4zjgYR844OM44GLfOODjOOCq3zjg4zjgY1844OM44KtfOODjOOBj3zjg4zjgq9844OM44GRfOODjOOCsXzjg4zjgZN844OM44KzfOOBseOBhOOCguOBv3zjg5Hjg5HmtLt844G144GG44O7fOOBteOBhuKApnzjgbXjgYV8776M772pfOOBteOBj+OCieOBv3zjgbXjgY/jgonjgpPjgad844G644Gj44GffOOBuuOCjeOBuuOCjXzjg5rjg63jg5rjg618776N776f776b776N776f776bfOODleOCp+ODqXzjgbvjgYbjgZHjgYR844G844Gj44GNfOODneODq+ODjnzjgbzjgo3jgpN844Oc44Ot44OzfO++ju++nu++m+++nXzjgb3jgo3jgop844Od44Ot44OqfO++ju++n+++m+++mHzjg57jg7PjgY3jgaR844Oe44Oz44Kt44OEfOOBvuOCk+OBk3zjgb7jgIfjgZN844G+4pev44GTfOOBvuKXi+OBk3zjgb7il4/jgZN844Oe44Oz44KzfOODnuOAh+OCs3zjg57il6/jgrN844Oe4peL44KzfOODnuKXj+OCs3zjgb7jgpPjgZXjgpN844KC44Gj44GT44KKfOODouODg+OCs+ODqnzjgoLjgb/jgoLjgb9844Oi44Of44Oi44OffOODpOOBo+OBn3zjg6TjgaPjgaZ844Ok44KJfOOChOOCieOBm+OCjXzjg6Tjgop844Ok44KLfOODpOOCjHzjg6Tjgo1844Op44OW44ObfOODr+ODrOODoXzmhJvmtrJ85ZaYfOmZsOaguHzpmbDojI586Zmw5ZSHfOa3q+WkonzpmqDmr5t86Zmw5q+bfOeUo+OCgeOCi3zlpbPjga7lrZDjga7ml6V85rGa44Gj44GV44KTfOWnpnzpqI7kuZfkvY185beo5qC5fOW3qOODgeODs3zlt6jnj4186YeR546JfOaciOe1jHzlvozog4zkvY185a2Q56iufOWtkOS9nOOCinzlsITnsr585L+h6ICFfOeyvua2snzpgI/jgZF85oCn5LqkfOeyvuWtkHzmraPluLjkvY185oCn5b60fOaAp+eahHznlJ/nkIZ85a+45q2i44KBfOe0oOadkHzmirHjgYR85oqx44GLfOaKseOBjXzmirHjgY985oqx44GRfOaKseOBk3zkvZPmtrJ85Lmz6aaWfOaBpeWeonznj43mo5J85Lit44Gg44GXfOS4reWHuuOBl3zlsL985oqc44GEfOaKnOOBkeOBquOBhHzmipzjgZHjgot85oqc44GR44KMfOeKr+ePjXzohqjjgol85YyF6IyOfOWLg+i1t3zmkannvoV86a2U576FfOaPieOBvnzmj4njgb985o+J44KAfOaPieOCgXzmvKvmuZZ844CH772efOKXr++9nnzil4vvvZ584peP772efOOAh+ODg+OCr+OCuXzil6/jg4Pjgq/jgrl84peL44OD44Kv44K5fOKXj+ODg+OCr+OCuQ==', 'base64').toString());\n\n // 「罵倒や差別的な表現を含むコメントをミュートする」のフィルタ正規表現\n static readonly mute_abusive_discriminatory_prejudiced_comments_pattern = new RegExp(Buffer.from('44CCfOOCouODi+ODl+ODrOOBj+OCk3zjgqLjg4vjg5fjg6zlkJt844Ki44K544OafOOCpOOCq+OCjHzjgYTjgb7jgYTjgaF844Kk44Oe44Kk44OBfOOCpOODqeOBpOOBj3zjgqbjgrh844Km44O844OofOOCpuODqHzjgqbjg6jjgq9844Km44OyfOOBjeOCguOBhHzjgq3jg6LjgqR844Kt44Oi44GEfOOCrS/jg6Av44OBfOOCrOOCpOOCuHzvvbbvvp7vvbLvvbzvvp5844Ks44KtfOOCq+OCuXzjgq3jg4Pjgrp844GN44Gh44GM44GEfOOCreODgeOCrOOCpHzjgq3jg6Djg4F844K344OKfOOCueODhuODnnzjgaTjgb7jgonjgap844Gk44G+44KJ44KTfOODgeODp+ODg+ODkeODqnzjg4Hjg6fjg7N85Y2D44On44OzfOOBpOOCk+OBvHzjg4Tjg7Pjg5x844ON44OI44Km44OofOOBq+OBoOOBguOBgnzjg4vjg4B85LqM44OAfO++hu++gO++nnzjg5Hjg7zjg6h844OR44OofOODkeODqOOCr3zjgbbjgaPjgZV844OW44OD44K1fOOBtuOBleOBhHzjg5bjgrXjgqR844G+44Gs44GRfOODoeOCr+ODqXzjg5Djgqt844Og44Kr44Gk44GPfOiNkuOCieOBl3zpurvnlJ/jgrvjg6Hjg7Pjg4h85oWw5a6J5ammfOWus+WFkHzlpJblrZd85aem5Zu9fOmfk+WbvXzpn5PkuK186Z+T5pelfOWfuuWcsOWklnzmsJfni4LjgYR85rCX6YGV44GEfOWIh+OBo+OBn3zliIfjgaPjgaZ85rCX5oyB44Gh5oKqfOWbveS6pOaWree1tnzmrrp86aCDfOmgg+OBl3zpoIPjgZl86aCD44GbfOWcqOaXpXzlj4LmlL/mqKl85q2744GtfOawj+OBrXzvvoDvvot85q255YyVfOatueODknzlpLHpgJ986Zqc5a6zfOaWreS6pHzkuK3pn5N85pyd6a6ufOW+tOeUqOW3pXzlo7p85aO3fOWjvHzml6Xpn5N85pel5bidfOeymOedgHzlj43ml6V86aas6bm/fOeZuueLgnznmbrpgZR85py0fOWjsuWbvXzkuI3lv6t85L215ZCIfOmWk+aKnOOBkXzmloflj6V86Z2W5Zu9', 'base64').toString());\n\n // 「8文字以上同じ文字が連続しているコメントをミュートする」のフィルタ正規表現\n static readonly mute_consecutive_same_characters_comments_pattern = /(.)\\1{7,}/;\n\n // ニコ生の特殊コマンド付きコメントのフィルタ正規表現\n static readonly special_command_comments_pattern = /\\/[a-z]+ /;\n\n // 迷惑な統計コメントのフィルタ正規表現\n static readonly annoying_statistical_comments_pattern = /最高\\d+米\\/|計\\d+ID|総\\d+米/;\n\n // ニコニコの色指定を 16 進数カラーコードに置換するテーブル\n static readonly color_table: {[key: string]: string} = {\n 'white': '#FFEAEA',\n 'red': '#F02840',\n 'pink': '#FD7E80',\n 'orange': '#FDA708',\n 'yellow': '#FFE133',\n 'green': '#64DD17',\n 'cyan': '#00D4F5',\n 'blue': '#4763FF',\n 'purple': '#D500F9',\n 'black': '#1E1310',\n 'white2': '#CCCC99',\n 'niconicowhite': '#CCCC99',\n 'red2': '#CC0033',\n 'truered': '#CC0033',\n 'pink2': '#FF33CC',\n 'orange2': '#FF6600',\n 'passionorange': '#FF6600',\n 'yellow2': '#999900',\n 'madyellow': '#999900',\n 'green2': '#00CC66',\n 'elementalgreen': '#00CC66',\n 'cyan2': '#00CCCC',\n 'blue2': '#3399FF',\n 'marineblue': '#3399FF',\n 'purple2': '#6633CC',\n 'nobleviolet': '#6633CC',\n 'black2': '#666666',\n };\n\n\n /**\n * ニコニコの色指定を 16 進数カラーコードに置換する\n * @param color ニコニコの色指定\n * @return 16 進数カラーコード\n */\n static getCommentColor(color: string): string | null {\n return this.color_table[color] || null;\n }\n\n\n /**\n * ニコニコの位置指定を DPlayer の位置指定に置換する\n * @param position ニコニコの位置指定\n * @return DPlayer の位置指定\n */\n static getCommentPosition(position: string): 'top' | 'right' | 'bottom' | null {\n switch (position) {\n case 'ue':\n return 'top';\n case 'naka':\n return 'right';\n case 'shita':\n return 'bottom';\n default:\n return null;\n }\n }\n\n\n /**\n * ニコニコのサイズ指定を DPlayer のサイズ指定に置換する\n * @param size ニコニコのサイズ指定\n * @returns DPlayer のサイズ指定\n */\n static getCommentSize(size: string): 'big' | 'medium' | 'small' | null {\n switch (size) {\n case 'big':\n case 'medium':\n case 'small':\n return size;\n default:\n return null;\n }\n }\n\n\n /**\n * ニコニコのコメントコマンドを解析する\n * @param comment_mail ニコニコのコメントコマンド\n * @returns コメントの色、位置、サイズ\n */\n static parseCommentCommand(comment_mail: string): {\n color: string;\n position: 'top' | 'right' | 'bottom';\n size: 'big' | 'medium' | 'small';\n } {\n let color = '#FFEAEA';\n let position: 'top' | 'right' | 'bottom' = 'right';\n let size: 'big' | 'medium' | 'small' = 'medium';\n\n if (comment_mail !== undefined && comment_mail !== null) {\n const commands = comment_mail.replace('184', '').split(' ');\n\n for (const command of commands) {\n const parsed_color = CommentUtils.getCommentColor(command);\n const parsed_position = CommentUtils.getCommentPosition(command);\n const parsed_size = CommentUtils.getCommentSize(command);\n if (parsed_color !== null) {\n color = parsed_color;\n }\n if (parsed_position !== null) {\n position = parsed_position;\n }\n if (parsed_size !== null) {\n size = parsed_size;\n }\n }\n }\n\n return {color, position, size};\n }\n\n\n /**\n * ミュート対象のコメントかどうかを判断する\n * @param comment コメント\n * @param user_id コメントを投稿したユーザーの ID\n * @param color コメントの色\n * @param position コメントの位置\n * @param size コメントのサイズ\n * @return ミュート対象のコメントなら true を返す\n */\n static isMutedComment(\n comment: string,\n user_id: string,\n color?: string,\n position?: 'top' | 'right' | 'bottom',\n size?: 'big' | 'medium' | 'small',\n ): boolean {\n\n const settings_store = useSettingsStore();\n\n // ユーザー ID ミュート処理\n if (settings_store.settings.muted_niconico_user_ids.includes(user_id)) {\n return true;\n }\n\n // ニコ生の特殊コマンド付きコメント (/nicoad, /emotion など) を一括で弾く\n if (CommentUtils.special_command_comments_pattern.test(comment)) {\n return true;\n }\n\n // 「映像の上下に固定表示されるコメントをミュートする」がオンの場合\n // コメントの位置が top (上固定) もしくは bottom (下固定) のときは弾く\n if (settings_store.settings.mute_fixed_comments === true && (position === 'top' || position === 'bottom')) {\n console.log('[CommentUtils] Muted comment (fixed_comments): ' + comment);\n return true;\n }\n\n // 「色付きのコメントをミュートする」がオンの場合\n // コメントの色が #FFEAEA (デフォルト) 以外のときは弾く\n if (settings_store.settings.mute_colored_comments === true && color !== '#FFEAEA') {\n console.log('[CommentUtils] Muted comment (colored_comments): ' + comment);\n return true;\n }\n\n // 「文字サイズが大きいコメントをミュートする」がオンの場合\n // コメントのサイズが big のときは弾く\n if (settings_store.settings.mute_big_size_comments === true && size === 'big') {\n console.log('[CommentUtils] Muted comment (big_size_comments): ' + comment);\n return true;\n }\n\n // 「露骨な表現を含むコメントをミュートする」がオンの場合\n if ((settings_store.settings.mute_vulgar_comments === true) &&\n (CommentUtils.mute_vulgar_comments_pattern.test(comment))) {\n console.log('[CommentUtils] Muted comment (vulgar_comments): ' + comment);\n return true;\n }\n\n // 「罵倒や差別的な表現を含むコメントをミュートする」がオンの場合\n if ((settings_store.settings.mute_abusive_discriminatory_prejudiced_comments === true) &&\n (CommentUtils.mute_abusive_discriminatory_prejudiced_comments_pattern.test(comment))) {\n console.log('[CommentUtils] Muted comment (abusive_discriminatory_prejudiced_comments): ' + comment);\n return true;\n }\n\n // 「8文字以上同じ文字が連続しているコメントをミュートする」がオンの場合\n if ((settings_store.settings.mute_consecutive_same_characters_comments === true &&\n (CommentUtils.mute_consecutive_same_characters_comments_pattern.test(comment)))) {\n console.log('[CommentUtils] Muted comment (consecutive_same_characters_comments): ' + comment);\n return true;\n }\n\n // キーワードミュート処理\n for (const muted_comment_keyword of settings_store.settings.muted_comment_keywords) {\n if (muted_comment_keyword.pattern === '') continue; // キーワードが空文字のときは無視\n switch (muted_comment_keyword.match) {\n // 部分一致\n case 'partial':\n if (comment.includes(muted_comment_keyword.pattern)) {\n console.log('[CommentUtils] Muted comment (partial): ' + comment);\n return true;\n }\n break;\n // 前方一致\n case 'forward':\n if (comment.startsWith(muted_comment_keyword.pattern)) {\n console.log('[CommentUtils] Muted comment (forward): ' + comment);\n return true;\n }\n break;\n // 後方一致\n case 'backward':\n if (comment.endsWith(muted_comment_keyword.pattern)) {\n console.log('[CommentUtils] Muted comment (backward): ' + comment);\n return true;\n }\n break;\n // 完全一致\n case 'exact':\n if (comment === muted_comment_keyword.pattern) {\n console.log('[CommentUtils] Muted comment (exact): ' + comment);\n return true;\n }\n break;\n // 正規表現\n case 'regex':\n if (new RegExp(muted_comment_keyword.pattern).test(comment)) {\n console.log('[CommentUtils] Muted comment (regex): ' + comment);\n return true;\n }\n break;\n }\n }\n\n // 「NHK→計1447ID/内プレ425ID/総33372米 ◆ Eテレ → 計73ID/内プレ19ID/総941米」のような\n // 迷惑コメントを一括で弾く (あえてミュートしたくないユースケースが思い浮かばないのでデフォルトで弾く)\n // 一番最後なのは、この迷惑コメント自体の頻度が低いため\n if (CommentUtils.annoying_statistical_comments_pattern.test(comment)) {\n return true;\n }\n\n // いずれのミュート処理にも引っかからなかった (ミュート対象ではない)\n return false;\n }\n\n\n /**\n * ミュート済みキーワードリストに追加する (完全一致)\n * @param comment コメント文字列\n */\n static addMutedKeywords(comment: string): void {\n\n // すでにまったく同じミュート済みキーワードが追加済みの場合は何もしない\n const settings_store = useSettingsStore();\n for (const muted_comment_keyword of settings_store.settings.muted_comment_keywords) {\n if (muted_comment_keyword.match === 'exact' && muted_comment_keyword.pattern === comment) {\n return;\n }\n }\n\n // ミュート済みキーワードリストに追加\n settings_store.settings.muted_comment_keywords.push({\n match: 'exact',\n pattern: comment,\n });\n }\n\n\n /**\n * ミュート済みニコニコユーザー ID リストに追加する\n * @param user_id ニコニコユーザー ID\n */\n static addMutedNiconicoUserIDs(user_id: string): void {\n\n // すでに追加済みの場合は何もしない\n const settings_store = useSettingsStore();\n if (settings_store.settings.muted_niconico_user_ids.includes(user_id)) {\n return;\n }\n\n // ミュート済みニコニコユーザー ID リストに追加\n settings_store.settings.muted_niconico_user_ids.push(user_id);\n }\n}\n","\n/**\n * プレイヤー周りのユーティリティ\n */\nexport class PlayerUtils {\n\n /**\n * プレイヤーの背景画像をランダムで取得し、その URL を返す\n * @returns ランダムで設定されたプレイヤーの背景画像の URL\n */\n static generatePlayerBackgroundURL(): string {\n const background_count = 50; // 50種類から選択\n const random = (Math.floor(Math.random() * background_count) + 1);\n return `/assets/images/player-backgrounds/${random.toString().padStart(2, '0')}.jpg`;\n }\n\n\n /**\n * 現在のブラウザで H.265 / HEVC 映像が再生できるかどうかを取得する\n * ref: https://github.com/StaZhu/enable-chromium-hevc-hardware-decoding#mediacapabilities\n * @returns 再生できるなら true、できないなら false\n */\n static isHEVCVideoSupported(): boolean {\n // hvc1.1.6.L123.B0 の部分は呪文 (HEVC であることと、そのプロファイルを示す値らしい)\n return document.createElement('video').canPlayType('video/mp4; codecs=\"hvc1.1.6.L123.B0\"') === 'probably';\n }\n}\n","\nimport dayjs from 'dayjs';\nimport 'dayjs/locale/ja';\nimport isBetween from 'dayjs/plugin/isBetween';\nimport isSameOrAfter from 'dayjs/plugin/isSameOrAfter';\nimport isSameOrBefore from 'dayjs/plugin/isSameOrBefore';\n\nimport { IProgram } from '@/services/Programs';\nimport Utils from '@/utils';\n\n\n/**\n * 番組情報周りのユーティリティ\n */\nexport class ProgramUtils {\n\n /**\n * 番組情報中の[字]や[解]などの記号をいい感じに装飾する\n * @param program 番組情報のオブジェクト\n * @param key 番組情報のオブジェクトから取り出すプロパティのキー\n * @returns 装飾した文字列\n */\n static decorateProgramInfo(program: IProgram | null, key: string): string {\n\n // program が空でないかつ、program[key] が存在する\n if (program !== null && program[key] !== null) {\n\n // 番組情報に含まれる HTML の特殊文字で表示がバグらないように、事前に HTML エスケープしておく\n const text = Utils.escapeHTML(program[key]);\n\n // 本来 ARIB 外字である記号の一覧\n // ref: https://ja.wikipedia.org/wiki/%E7%95%AA%E7%B5%84%E8%A1%A8\n // ref: https://github.com/xtne6f/EDCB/blob/work-plus-s/EpgDataCap3/EpgDataCap3/ARIB8CharDecode.cpp#L1319\n const mark = '新|終|再|交|映|手|声|多|副|字|文|CC|OP|二|S|B|SS|無|無料' +\n 'C|S1|S2|S3|MV|双|デ|D|N|W|P|H|HV|SD|天|解|料|前|後初|生|販|吹|PPV|' +\n '演|移|他|収|・|英|韓|中|字/日|字/日英|3D|2K|4K|8K|5.1|7.1|22.2|60P|120P|d|HC|HDR|SHV|UHD|VOD|配|初';\n\n // 正規表現を作成\n const pattern1 = new RegExp('\\\\((二|字|再)\\\\)', 'g'); // 通常の括弧で囲まれている記号\n const pattern2 = new RegExp(`\\\\[(${mark})\\\\]`, 'g');\n\n // 正規表現で置換した結果を返す\n return text.replace(pattern1, '$1')\n .replace(pattern2, '$1');\n\n // 番組情報がない時間帯\n } else {\n\n dayjs.extend(isSameOrAfter);\n dayjs.extend(isSameOrBefore);\n dayjs.extend(isBetween);\n\n // 23時~翌7時 (0:00 ~ 06:59 or 23:00 ~ 23:59) の間なら放送を休止している可能性が高いので、放送休止と表示する\n const now = dayjs();\n const pause_time_start = dayjs().hour(0).minute(0).second(0);\n const pause_time_end = dayjs().hour(6).minute(59).second(59);\n const pause_time_start_23 = dayjs().hour(23).minute(0).second(0);\n const pause_time_end_23 = dayjs().hour(23).minute(59).second(59);\n if ((now.isSameOrAfter(pause_time_start) && now.isSameOrBefore(pause_time_end)) ||\n (now.isSameOrAfter(pause_time_start_23) && now.isSameOrBefore(pause_time_end_23))) {\n if (key === 'title') {\n return '放送休止'; // タイトル\n } else {\n return 'この時間は放送を休止しています。'; // 番組概要\n }\n\n // それ以外の時間帯では、「番組情報がありません」と表示する\n // 急な番組変更の影響で、一時的にその時間帯に対応する番組情報が消えることがある\n // 特に Mirakurun バックエンドでは高頻度で収集した EIT[p/f] が比較的すぐ反映されるため、この現象が起こりやすい\n // 日中に放送休止(停波)になることはまずあり得ないので、番組情報が取得できてないだけで視聴できるかも?というニュアンスを与える\n } else {\n if (key === 'title') {\n return '番組情報がありません'; // タイトル\n } else {\n return 'この時間の番組情報を取得できませんでした。'; // 番組概要\n }\n }\n }\n }\n\n\n /**\n * オブジェクトからプロパティを取得し、もしプロパティが存在しなければ代替値を返す\n * @param items 対象のオブジェクト\n * @param key オブジェクトから取り出すプロパティのキー\n * @param default_value 取得できなかった際の代替値\n * @returns オブジェクト取得した値 or 代替値\n */\n static getAttribute(items: {[key: string]: any}, key: string, default_value: any): any {\n\n // items が空でないかつ、items[key] が存在する\n if (items !== null && items[key] !== undefined && items[key] !== null) {\n\n // items[key] の内容を返す\n return items[key];\n\n // 指定された代替値を返す\n } else {\n return default_value;\n }\n }\n\n\n /**\n * 番組の進捗状況を取得する\n * @param program 番組情報\n * @returns 番組の進捗状況(%単位)\n */\n static getProgramProgress(program: IProgram): number {\n\n // program が空でない\n if (program !== null) {\n\n // 番組開始時刻から何秒進んだか\n const progress = dayjs(dayjs()).diff(program.start_time, 'second');\n\n // %単位の割合を算出して返す\n return progress / program.duration * 100;\n\n // 放送休止中\n } else {\n return 0;\n }\n }\n\n\n /**\n * 番組の放送時刻を取得する\n * @param program 番組情報\n * @param is_short 時刻のみ返すかどうか\n * @returns 番組の放送時刻\n */\n static getProgramTime(program: IProgram, is_short: boolean = false): string {\n\n // program が空でなく、かつ番組時刻が初期値でない\n if (program !== null && program.start_time !== '2000-01-01T00:00:00+09:00') {\n\n // dayjs で日付を扱いやすく\n dayjs.locale('ja'); // ロケールを日本に設定\n const start_time = dayjs(program.start_time);\n const end_time = dayjs(program.end_time);\n const duration = program.duration / 60; // 分換算\n\n // 時刻のみ返す\n if (is_short === true) { // 時刻のみ\n return `${start_time.format('HH:mm')} ~ ${end_time.format('HH:mm')}`;\n // 通常\n } else {\n return `${start_time.format('YYYY/MM/DD (dd) HH:mm')} ~ ${end_time.format('HH:mm')} (${duration}分)`;\n }\n\n // 放送休止中\n } else {\n\n // 時刻のみ返す\n if (is_short === true) {\n return '--:-- ~ --:--';\n // 通常\n } else {\n return '----/--/-- (-) --:-- ~ --:-- (--分)';\n }\n }\n }\n}\n","\n// 共通ユーティリティをデフォルトとしてインポート\nimport Utils from '@/utils/Utils';\nexport default Utils;\n\n// Utils フォルダ配下のユーティリティを一括でインポートできるように\nexport * from '@/utils/ChannelUtils';\nexport * from '@/utils/CommentUtils';\nexport * from '@/utils/PlayerUtils';\nexport * from '@/utils/ProgramUtils';\n","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"route-container\"},[_c('Header'),_c('main',[_c('Navigation'),_c('div',{staticClass:\"login-container-wrapper d-flex align-center w-100 mb-13\"},[_c('v-card',{staticClass:\"login-container px-10 pt-8 pb-11 mx-auto background lighten-1\",attrs:{\"elevation\":\"10\",\"width\":\"100%\",\"max-width\":\"450\"}},[_c('v-card-title',{staticClass:\"login__logo flex-column justify-center\"},[_c('v-img',{attrs:{\"max-width\":\"250\",\"src\":\"/assets/images/logo.svg\"}}),_c('h4',{staticClass:\"mt-10\"},[_vm._v(\"ログイン\")])],1),_c('v-divider'),_c('v-form',{ref:\"login\",on:{\"submit\":function($event){$event.preventDefault();}}},[_c('v-text-field',{staticClass:\"mt-12\",attrs:{\"outlined\":\"\",\"placeholder\":\"ユーザー名\",\"hide-details\":\"\",\"autofocus\":\"\",\"dense\":_vm.is_form_dense},model:{value:(_vm.username),callback:function ($$v) {_vm.username=$$v},expression:\"username\"}}),_c('v-text-field',{staticClass:\"mt-8\",attrs:{\"outlined\":\"\",\"placeholder\":\"パスワード\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"type\":_vm.password_showing ? 'text' : 'password',\"append-icon\":_vm.password_showing ? 'mdi-eye' : 'mdi-eye-off'},on:{\"click:append\":function($event){_vm.password_showing = !_vm.password_showing}},model:{value:(_vm.password),callback:function ($$v) {_vm.password=$$v},expression:\"password\"}}),_c('v-btn',{staticClass:\"login-button mt-5\",attrs:{\"color\":\"secondary\",\"depressed\":\"\",\"width\":\"100%\",\"height\":\"56\"},on:{\"click\":function($event){return _vm.login()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fa:sign-in\"}}),_vm._v(\"ログイン \")],1)],1)],1)],1)],1)],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('header',{staticClass:\"header\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"konomitv-logo ml-3 ml-md-6\",attrs:{\"to\":\"/tv/\"}},[_c('img',{staticClass:\"konomitv-logo__image\",attrs:{\"src\":\"/assets/images/logo.svg\",\"height\":\"21\"}})]),_c('v-spacer')],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","import { render, staticRenderFns } from \"./Header.vue?vue&type=template&id=84897154&scoped=true&\"\nvar script = {}\nimport style0 from \"./Header.vue?vue&type=style&index=0&id=84897154&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"84897154\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',[_c('div',{staticClass:\"navigation-container elevation-8\"},[_c('nav',{staticClass:\"navigation\"},[_c('div',{staticClass:\"navigation-scroll\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"navigation__link\",attrs:{\"active-class\":\"navigation__link--active\",\"to\":\"/tv/\"}},[_c('Icon',{staticClass:\"navigation__link-icon\",attrs:{\"icon\":\"fluent:tv-20-regular\",\"width\":\"26px\"}}),_c('span',{staticClass:\"navigation__link-text\"},[_vm._v(\"テレビをみる\")])],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"navigation__link\",attrs:{\"active-class\":\"navigation__link--active\",\"to\":\"/videos/\"}},[_c('Icon',{staticClass:\"navigation__link-icon\",attrs:{\"icon\":\"fluent:movies-and-tv-20-regular\",\"width\":\"26px\"}}),_c('span',{staticClass:\"navigation__link-text\"},[_vm._v(\"ビデオをみる\")])],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"navigation__link\",attrs:{\"active-class\":\"navigation__link--active\",\"to\":\"/timetable/\"}},[_c('Icon',{staticClass:\"navigation__link-icon\",attrs:{\"icon\":\"fluent:calendar-ltr-20-regular\",\"width\":\"26px\"}}),_c('span',{staticClass:\"navigation__link-text\"},[_vm._v(\"番組表\")])],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"navigation__link\",attrs:{\"active-class\":\"navigation__link--active\",\"to\":\"/reserves/\"}},[_c('Icon',{staticClass:\"navigation__link-icon\",staticStyle:{\"padding\":\"0.5px\"},attrs:{\"icon\":\"fluent:timer-16-regular\",\"width\":\"26px\"}}),_c('span',{staticClass:\"navigation__link-text\"},[_vm._v(\"録画予約\")])],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"navigation__link\",attrs:{\"active-class\":\"navigation__link--active\",\"to\":\"/mylist/\"}},[_c('Icon',{staticClass:\"navigation__link-icon\",attrs:{\"icon\":\"ic:round-playlist-play\",\"width\":\"26px\"}}),_c('span',{staticClass:\"navigation__link-text\"},[_vm._v(\"マイリスト\")])],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"navigation__link\",attrs:{\"active-class\":\"navigation__link--active\",\"to\":\"/captures/\"}},[_c('Icon',{staticClass:\"navigation__link-icon\",attrs:{\"icon\":\"fluent:image-multiple-24-regular\",\"width\":\"26px\"}}),_c('span',{staticClass:\"navigation__link-text\"},[_vm._v(\"キャプチャ\")])],1),_c('v-spacer'),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"navigation__link\",attrs:{\"active-class\":\"navigation__link--active\",\"to\":\"/settings/\"}},[_c('Icon',{staticClass:\"navigation__link-icon\",attrs:{\"icon\":\"fluent:settings-20-regular\",\"width\":\"26px\"}}),_c('span',{staticClass:\"navigation__link-text\"},[_vm._v(\"設定\")])],1),_c('a',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.top\",value:(_vm.versionStore.is_update_available ?\n `アップデートがあります (version ${_vm.versionStore.latest_version})` : ''),expression:\"versionStore.is_update_available ?\\n `アップデートがあります (version ${versionStore.latest_version})` : ''\",modifiers:{\"top\":true}}],staticClass:\"navigation__link\",class:{\n 'navigation__link--version': _vm.versionStore.is_client_develop_version,\n 'navigation__link--highlight': _vm.versionStore.is_update_available,\n },attrs:{\"active-class\":\"navigation__link--active\",\"href\":\"https://github.com/tsukumijima/KonomiTV\"}},[_c('Icon',{staticClass:\"navigation__link-icon\",attrs:{\"icon\":\"fluent:info-16-regular\",\"width\":\"26px\"}}),_c('span',{staticClass:\"navigation__link-text\"},[_vm._v(\"version \"+_vm._s(_vm.versionStore.client_version))])],1)],1)])]),_c('BottomNavigation')],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('v-bottom-navigation',{staticClass:\"bottom-navigation-container elevation-12\",attrs:{\"color\":\"primary\",\"grow\":\"\"}},[_c('v-btn',{staticClass:\"bottom-navigation-button\",attrs:{\"to\":\"/tv/\"}},[_c('span',{staticClass:\"mt-1\"},[_vm._v(\"テレビをみる\")]),_c('Icon',{attrs:{\"icon\":\"fluent:tv-20-regular\",\"width\":\"30px\"}})],1),_c('v-btn',{staticClass:\"bottom-navigation-button\",attrs:{\"to\":\"/videos/\"}},[_c('span',{staticClass:\"mt-1\"},[_vm._v(\"ビデオをみる\")]),_c('Icon',{attrs:{\"icon\":\"fluent:movies-and-tv-20-regular\",\"width\":\"30px\"}})],1),_c('v-btn',{staticClass:\"bottom-navigation-button\",attrs:{\"to\":\"/settings/\"}},[_c('span',{staticClass:\"mt-1\"},[_vm._v(\"設定\")]),_c('Icon',{attrs:{\"icon\":\"fluent:settings-20-regular\",\"width\":\"30px\"}})],1)],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","import { render, staticRenderFns } from \"./BottomNavigation.vue?vue&type=template&id=3df53df3&scoped=true&\"\nvar script = {}\nimport style0 from \"./BottomNavigation.vue?vue&type=style&index=0&id=3df53df3&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"3df53df3\",\n null\n \n)\n\nexport default component.exports","\nimport APIClient from '@/services/APIClient';\n\n\n/** バージョン情報を表すインターフェイス */\nexport interface IVersionInformation {\n version: string;\n latest_version: string;\n environment: 'Windows' | 'Linux' | 'Linux-Docker' | 'Linux-ARM';\n backend: 'EDCB' | 'Mirakurun';\n encoder: 'FFmpeg' | 'QSVEncC' | 'NVEncC' | 'VCEEncC' | 'rkmppenc';\n}\n\n\nclass Version {\n\n /**\n * バージョン情報を取得する\n * @returns バージョン情報 or バージョン情報の取得に失敗した場合は null\n */\n static async fetchServerVersion(): Promise {\n\n // API リクエストを実行\n const response = await APIClient.get('/version');\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'バージョン情報を取得できませんでした。');\n return null;\n }\n\n return response.data;\n }\n}\n\nexport default Version;\n","\nimport { defineStore } from 'pinia';\n\nimport Version, { IVersionInformation } from '@/services/Version';\nimport Utils from '@/utils';\n\n\n/**\n * 現在ログイン中のユーザーアカウントの情報を共有するストア\n */\nconst useVersionStore = defineStore('version', {\n state: () => ({\n\n // サーバーのバージョン情報\n server_version_info: null as IVersionInformation | null,\n\n // 最終更新日時 (UNIX タイムスタンプ、秒単位)\n last_updated_at: 0,\n }),\n getters: {\n client_version(): string {\n return Utils.version;\n },\n server_version(): string | null {\n return this.server_version_info?.version ?? null;\n },\n latest_version(): string | null {\n return this.server_version_info?.latest_version ?? null;\n },\n is_client_develop_version(): boolean {\n return this.client_version.includes('-dev');\n },\n is_server_develop_version(): boolean {\n return this.server_version?.includes('-dev') ?? false;\n },\n is_update_available(): boolean {\n // もし現在のサーバーバージョンと最新のバージョンが異なるなら、アップデートが利用できると判断する\n // 現在のサーバーバージョンが開発版 (-dev あり) で、かつ最新のバージョンがリリース版 (-dev なし) の場合も同様に表示する\n // つまり開発版だと同じバージョンのリリース版がリリースされたときにしかアップデート通知が表示されない事になるが、ひとまずこれで…\n if (this.server_version === null || this.latest_version === null) return false;\n if ((this.is_server_develop_version === false && this.server_version !== this.latest_version) ||\n (this.is_server_develop_version === true && this.server_version.replace('-dev', '') === this.latest_version)) {\n return true;\n }\n return false;\n },\n is_version_mismatch(): boolean {\n if (this.server_version === null) return false;\n return this.client_version !== this.server_version;\n }\n },\n actions: {\n\n /**\n * バージョン情報を取得する\n * すでに取得済みの情報がある場合は API リクエストを行わずにそれを返す\n * @param force 強制的に API リクエストを行う場合は true\n * @returns バージョン情報 or バージョン情報の取得に失敗した場合は null\n */\n async fetchServerVersion(force: boolean = false): Promise {\n\n // バージョン情報がある場合はそれを返す\n // force が true の場合は無視される\n if (this.server_version_info !== null && force === false) {\n // ただし、最終更新日時が1分以上前の場合は非同期で更新する\n if (Utils.time() - this.last_updated_at > 60) {\n this.fetchServerVersion(true);\n }\n return this.server_version_info;\n }\n\n // サーバーのバージョン情報を取得する\n const version_info = await Version.fetchServerVersion();\n if (version_info === null) {\n return null;\n }\n this.server_version_info = version_info;\n this.last_updated_at = Utils.time();\n\n return this.server_version_info;\n },\n }\n});\n\nexport default useVersionStore;\n","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport BottomNavigation from '@/components/BottomNavigation.vue';\nimport useVersionStore from '@/store/VersionStore';\n\nexport default Vue.extend({\n name: 'Navigation',\n components: {\n BottomNavigation,\n },\n computed: {\n ...mapStores(useVersionStore),\n },\n async created() {\n await this.versionStore.fetchServerVersion();\n }\n});\n\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Navigation.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Navigation.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Navigation.vue?vue&type=template&id=5b40940b&scoped=true&\"\nimport script from \"./Navigation.vue?vue&type=script&lang=ts&\"\nexport * from \"./Navigation.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Navigation.vue?vue&type=style&index=0&id=5b40940b&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"5b40940b\",\n null\n \n)\n\nexport default component.exports","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport Header from '@/components/Header.vue';\nimport Navigation from '@/components/Navigation.vue';\nimport useUserStore from '@/store/UserStore';\nimport Utils from '@/utils';\n\nexport default Vue.extend({\n name: 'Login',\n components: {\n Header,\n Navigation,\n },\n data() {\n return {\n\n // フォームを小さくするかどうか\n is_form_dense: Utils.isSmartphoneHorizontal(),\n\n username: '' as string,\n password: '' as string,\n password_showing: false,\n };\n },\n computed: {\n // UserStore に this.userStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useUserStore),\n },\n async created() {\n\n // アカウント情報を更新\n await this.userStore.fetchUser();\n\n // 現在ログイン中の場合はアカウントページに遷移\n if (this.userStore.is_logged_in) {\n await this.$router.replace({path: '/settings/account'});\n }\n },\n methods: {\n async login() {\n\n // ユーザー名またはパスワードが空\n if (this.username === '' || this.password === '') {\n this.$message.error('ユーザー名またはパスワードが空です。');\n return;\n }\n\n // ログイン処理 (エラーハンドリング含む) を実行\n const result = await this.userStore.login(this.username, this.password);\n if (result === false) {\n return; // ログイン失敗\n }\n\n // アカウントページに遷移\n // ブラウザバックでログインページに戻れないようにする\n await this.$router.replace({path: '/settings/account'});\n }\n }\n});\n\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Login.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Login.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Login.vue?vue&type=template&id=851c3dec&scoped=true&\"\nimport script from \"./Login.vue?vue&type=script&lang=ts&\"\nexport * from \"./Login.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Login.vue?vue&type=style&index=0&id=851c3dec&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"851c3dec\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"route-container\"},[_c('Header'),_c('main',[_c('Navigation'),_vm._m(0)],1)],1)\n}\nvar staticRenderFns = [function (){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"d-flex justify-center align-center w-100\"},[_c('div',{staticClass:\"d-flex justify-center align-center flex-column\"},[_c('h1',[_vm._v(\"Not Found, or Under Development...\")]),_c('span',{staticClass:\"mt-4 text--text text--darken-1\"},[_vm._v(\"お探しのページは存在しないか、鋭意開発中です。\")])])])\n}]\n\nexport { render, staticRenderFns }","\n\nimport Vue from 'vue';\n\nimport Header from '@/components/Header.vue';\nimport Navigation from '@/components/Navigation.vue';\n\nexport default Vue.extend({\n name: 'NotFound',\n components: {\n Header,\n Navigation,\n },\n});\n\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./NotFound.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./NotFound.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./NotFound.vue?vue&type=template&id=1310cfee&scoped=true&\"\nimport script from \"./NotFound.vue?vue&type=script&lang=ts&\"\nexport * from \"./NotFound.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./NotFound.vue?vue&type=style&index=0&id=1310cfee&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"1310cfee\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"route-container\"},[_c('Header'),_c('main',[_c('Navigation'),_c('div',{staticClass:\"register-container-wrapper d-flex align-center w-100 mb-13\"},[_c('v-card',{staticClass:\"register-container px-10 pt-8 pb-11 mx-auto background lighten-1\",attrs:{\"elevation\":\"10\",\"width\":\"100%\",\"max-width\":\"450\"}},[_c('v-card-title',{staticClass:\"register__logo flex-column justify-center\"},[_c('v-img',{attrs:{\"max-width\":\"250\",\"src\":\"/assets/images/logo.svg\"}}),_c('h4',{staticClass:\"mt-10\"},[_vm._v(\"アカウントを作成\")])],1),_c('v-divider'),_c('v-form',{ref:\"register\",on:{\"submit\":function($event){$event.preventDefault();}}},[_c('v-text-field',{staticClass:\"mt-12\",attrs:{\"outlined\":\"\",\"placeholder\":\"ユーザー名\",\"autofocus\":\"\",\"dense\":_vm.is_form_dense,\"rules\":[_vm.username_validation]},model:{value:(_vm.username),callback:function ($$v) {_vm.username=$$v},expression:\"username\"}}),_c('v-text-field',{staticStyle:{\"margin-top\":\"2px\"},attrs:{\"outlined\":\"\",\"placeholder\":\"パスワード\",\"dense\":_vm.is_form_dense,\"type\":_vm.password_showing ? 'text' : 'password',\"append-icon\":_vm.password_showing ? 'mdi-eye' : 'mdi-eye-off',\"rules\":[_vm.password_validation]},on:{\"click:append\":function($event){_vm.password_showing = !_vm.password_showing}},model:{value:(_vm.password),callback:function ($$v) {_vm.password=$$v},expression:\"password\"}}),_c('v-btn',{staticClass:\"register-button mt-5\",attrs:{\"color\":\"secondary\",\"depressed\":\"\",\"width\":\"100%\",\"height\":\"56\"},on:{\"click\":function($event){return _vm.register()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:person-add-20-filled\",\"height\":\"24\"}}),_vm._v(\"アカウントを作成 \")],1)],1)],1)],1)],1)],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport Header from '@/components/Header.vue';\nimport Navigation from '@/components/Navigation.vue';\nimport useUserStore from '@/store/UserStore';\nimport Utils from '@/utils';\n\nexport default Vue.extend({\n name: 'Register',\n components: {\n Header,\n Navigation,\n },\n data() {\n return {\n\n // フォームを小さくするかどうか\n is_form_dense: Utils.isSmartphoneHorizontal(),\n\n username: null as string | null,\n username_validation: (value: string | null) => {\n if (value === '' || value === null) return 'ユーザー名を入力してください。';\n if (/^.{2,}$/.test(value) === false) return 'ユーザー名は2文字以上で入力してください。';\n return true;\n },\n password: null as string | null,\n password_showing: true, // アカウント作成時はデフォルトでパスワードを表示する\n password_validation: (value: string | null) => {\n if (value === '' || value === null) return 'パスワードを入力してください。';\n // 正規表現の参考: https://qiita.com/grrrr/items/0b35b5c1c98eebfa5128\n if (/^[a-zA-Z0-9!-/:-@¥[-`{-~]{4,}$/.test(value) === false) return 'パスワードは4文字以上の半角英数記号を入力してください。';\n return true;\n },\n };\n },\n computed: {\n // UserStore に this.userStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useUserStore),\n },\n async created() {\n\n // アカウント情報を更新\n await this.userStore.fetchUser();\n\n // 現在ログイン中の場合はアカウントページに遷移\n if (this.userStore.is_logged_in) {\n await this.$router.replace({path: '/settings/account'});\n }\n },\n methods: {\n async register() {\n\n // すべてのバリデーションが通過したときのみ\n // ref: https://qiita.com/Hijiri_Ishi/items/56cac99c8f3806a6fa24\n if ((this.$refs.register as any).validate() === false) return;\n if (this.username === null || this.password === null) return;\n\n // アカウント作成 & ログイン処理 (エラーハンドリング含む) を実行\n const result = await this.userStore.register(this.username, this.password);\n if (result === false) {\n return; // ログイン失敗\n }\n\n // アカウントページに遷移\n // ブラウザバックでアカウント作成画面に戻れないようにする\n await this.$router.replace({path: '/settings/account'});\n }\n }\n});\n\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Register.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Register.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Register.vue?vue&type=template&id=6533f3d0&scoped=true&\"\nimport script from \"./Register.vue?vue&type=script&lang=ts&\"\nexport * from \"./Register.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Register.vue?vue&type=style&index=0&id=6533f3d0&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"6533f3d0\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('SettingsBase',[_c('h2',{staticClass:\"settings__heading\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"settings__back-button\",attrs:{\"to\":\"/settings/\"}},[_c('Icon',{attrs:{\"icon\":\"fluent:arrow-left-12-filled\",\"width\":\"25px\"}})],1),_c('Icon',{attrs:{\"icon\":\"fluent:person-20-filled\",\"width\":\"25px\"}}),_c('span',{staticClass:\"ml-2\"},[_vm._v(\"アカウント\")])],1),_c('div',{staticClass:\"settings__content\",class:{'settings__content--loading': _vm.is_loading}},[(_vm.userStore.user === null)?_c('div',{staticClass:\"account\"},[_c('div',{staticClass:\"account-wrapper\"},[_c('img',{staticClass:\"account__icon\",attrs:{\"src\":\"/assets/images/account-icon-default.png\"}}),_c('div',{staticClass:\"account__info\"},[_c('div',{staticClass:\"account__info-name\"},[_c('span',{staticClass:\"account__info-name-text\"},[_vm._v(\"ログインしていません\")])]),_c('span',{staticClass:\"account__info-id\"},[_vm._v(\"Not logged in\")])])]),_c('v-btn',{staticClass:\"account__login ml-auto\",attrs:{\"color\":\"secondary\",\"width\":\"140\",\"height\":\"56\",\"depressed\":\"\",\"to\":\"/login/\"}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fa:sign-in\"}}),_vm._v(\"ログイン \")],1)],1):_vm._e(),(_vm.userStore.user !== null)?_c('div',{staticClass:\"account\"},[_c('div',{staticClass:\"account-wrapper\"},[_c('img',{staticClass:\"account__icon\",attrs:{\"src\":_vm.userStore.user_icon_url}}),_c('div',{staticClass:\"account__info\"},[_c('div',{staticClass:\"account__info-name\"},[_c('span',{staticClass:\"account__info-name-text\"},[_vm._v(_vm._s(_vm.userStore.user.name))]),(_vm.userStore.user.is_admin)?_c('span',{staticClass:\"account__info-admin\"},[_vm._v(\"管理者\")]):_vm._e()]),_c('span',{staticClass:\"account__info-id\"},[_vm._v(\"User ID: \"+_vm._s(_vm.userStore.user.id))])])]),_c('v-btn',{staticClass:\"account__login ml-auto\",attrs:{\"color\":\"secondary\",\"width\":\"140\",\"height\":\"56\",\"depressed\":\"\"},on:{\"click\":function($event){return _vm.userStore.logout()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fa:sign-out\"}}),_vm._v(\"ログアウト \")],1)],1):_vm._e(),(_vm.userStore.is_logged_in === false)?_c('div',{staticClass:\"account-register\"},[_c('div',{staticClass:\"account-register__heading\"},[_vm._v(\" KonomiTV アカウントにログインすると、\"),_c('br'),_vm._v(\"より便利な機能が使えます! \")]),_c('div',{staticClass:\"account-register__feature\"},[_c('div',{staticClass:\"account-feature\"},[_c('Icon',{staticClass:\"account-feature__icon\",attrs:{\"icon\":\"bi:chat-left-text-fill\"}}),_c('div',{staticClass:\"account-feature__info\"},[_c('span',{staticClass:\"account-feature__info-heading\"},[_vm._v(\"ニコニコ実況にコメントする\")]),_c('span',{staticClass:\"account-feature__info-text\"},[_vm._v(\"テレビを見ながらニコニコ実況にコメントできます。別途、ニコニコアカウントとの連携が必要です。\")])])],1),_c('div',{staticClass:\"account-feature\"},[_c('Icon',{staticClass:\"account-feature__icon\",attrs:{\"icon\":\"fa-brands:twitter\"}}),_c('div',{staticClass:\"account-feature__info\"},[_c('span',{staticClass:\"account-feature__info-heading\"},[_vm._v(\"Twitter 連携機能\")]),_c('span',{staticClass:\"account-feature__info-text\"},[_vm._v(\"テレビを見ながら Twitter にツイートしたり、検索したツイートをリアルタイムで表示できます。別途、Twitter アカウントとの連携が必要です。\")])])],1),_c('div',{staticClass:\"account-feature\"},[_c('Icon',{staticClass:\"account-feature__icon\",attrs:{\"icon\":\"fluent:arrow-sync-20-filled\"}}),_c('div',{staticClass:\"account-feature__info\"},[_c('span',{staticClass:\"account-feature__info-heading\"},[_vm._v(\"設定をデバイス間で同期\")]),_c('span',{staticClass:\"account-feature__info-text\"},[_vm._v(\"ピン留めしたチャンネルなど、ブラウザに保存されている各種設定をブラウザやデバイスをまたいで同期できます。\")])])],1),_c('div',{staticClass:\"account-feature\"},[_c('Icon',{staticClass:\"account-feature__icon\",attrs:{\"icon\":\"fa-solid:sliders-h\"}}),_c('div',{staticClass:\"account-feature__info\"},[_c('span',{staticClass:\"account-feature__info-heading\"},[_vm._v(\"環境設定をブラウザから変更\")]),_c('span',{staticClass:\"account-feature__info-text\"},[_vm._v(\"管理者権限があれば、環境設定をブラウザから変更できます。一番最初に作成されたアカウントには、自動で管理者権限が付与されます。\")])])],1)]),_c('div',{staticClass:\"account-register__description\"},[_vm._v(\" KonomiTV アカウントの作成に必要なものは\"),_c('br',{staticClass:\"smartphone-vertical-only\"}),_vm._v(\"ユーザー名とパスワードだけです。\"),_c('br'),_vm._v(\" アカウントはローカルにインストールした\"),_c('br',{staticClass:\"smartphone-vertical-only\"}),_vm._v(\" KonomiTV サーバーごとに保存されます。\"),_c('br'),_vm._v(\" 外部のサービスには保存されませんので、ご安心ください。\"),_c('br')]),_c('v-btn',{staticClass:\"account-register__button\",attrs:{\"color\":\"secondary\",\"width\":\"100%\",\"max-width\":\"250\",\"height\":\"50\",\"depressed\":\"\",\"to\":\"/register/\"}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:person-add-20-filled\",\"height\":\"24\"}}),_vm._v(\"アカウントを作成 \")],1)],1):_vm._e(),(_vm.userStore.is_logged_in === true)?_c('div',[_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"sync_settings\"}},[_vm._v(\"設定をデバイス間で同期する\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"sync_settings\"}},[_vm._v(\" KonomiTV では、設定を同じアカウントでログインしているデバイス間で同期できます!\"),_c('br'),_vm._v(\" 同期をオンにすると、同期をオンにしているすべてのデバイスで共通の設定が使えます。ピン留めチャンネルやハッシュタグリストなども同期されます。\"),_c('br'),_vm._v(\" なお、デバイス固有の設定(画質設定など)は、同期後も各デバイスで個別に反映されます。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"sync_settings\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.sync_settings),callback:function ($$v) {_vm.sync_settings=$$v},expression:\"sync_settings\"}})],1),_c('v-dialog',{attrs:{\"max-width\":\"530\"},model:{value:(_vm.sync_settings_dialog),callback:function ($$v) {_vm.sync_settings_dialog=$$v},expression:\"sync_settings_dialog\"}},[_c('v-card',[_c('v-card-title',{staticClass:\"justify-center\"},[_vm._v(\"設定データの競合\")]),_c('v-card-text',[_vm._v(\" このデバイスの設定と、サーバーに保存されている設定が競合しています。\"),_c('br'),_vm._v(\" 一度上書きすると、元に戻すことはできません。慎重に選択してください。\"),_c('br')]),_c('div',{staticClass:\"d-flex flex-column px-4 pb-4 settings__conflict-dialog\"},[_c('v-btn',{staticClass:\"settings__save-button error--text text--lighten-1\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.overrideServerSettingsFromClient()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:document-arrow-up-16-filled\",\"height\":\"22px\"}}),_vm._v(\" サーバーに保存されている設定を、\"),_c('br',{staticClass:\"smartphone-vertical-only\"}),_vm._v(\"このデバイスの設定で上書きする \")],1),_c('v-btn',{staticClass:\"settings__save-button error--text text--lighten-1 mt-3\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.overrideClientSettingsFromServer()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:document-arrow-down-16-filled\",\"height\":\"22px\"}}),_vm._v(\" このデバイスの設定を、\"),_c('br',{staticClass:\"smartphone-vertical-only\"}),_vm._v(\"サーバーに保存されている設定で上書きする \")],1),_c('v-btn',{staticClass:\"settings__save-button mt-3\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){_vm.sync_settings_dialog = false}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:dismiss-16-filled\",\"height\":\"22px\"}}),_vm._v(\" キャンセル \")],1)],1)],1)],1),_c('v-form',{ref:\"settings_username\",staticClass:\"settings__item\",on:{\"submit\":function($event){$event.preventDefault();}}},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"ユーザー名\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" KonomiTV アカウントのユーザー名を設定します。アルファベットだけでなく日本語や記号も使えます。\"),_c('br'),_vm._v(\" 同じ KonomiTV サーバー上の他のアカウントと同じユーザー名には変更できません。\"),_c('br')]),_c('v-text-field',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"placeholder\":\"ユーザー名\",\"dense\":_vm.is_form_dense,\"rules\":[_vm.settings_username_validation]},model:{value:(_vm.settings_username),callback:function ($$v) {_vm.settings_username=$$v},expression:\"settings_username\"}})],1),_c('v-btn',{staticClass:\"settings__save-button\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.updateAccountInfo('username')}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:save-16-filled\",\"height\":\"24px\"}}),_vm._v(\"ユーザー名を更新 \")],1),_c('v-form',{staticClass:\"settings__item\",on:{\"submit\":function($event){$event.preventDefault();}}},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"アイコン画像\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" KonomiTV アカウントのアイコン画像を設定します。\"),_c('br'),_vm._v(\" アップロードされた画像は自動で 400×400 の正方形にリサイズされます。\"),_c('br')]),_c('v-file-input',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"placeholder\":\"アイコン画像を選択\",\"dense\":_vm.is_form_dense,\"accept\":\"image/jpeg, image/png\",\"prepend-icon\":\"\",\"prepend-inner-icon\":\"mdi-paperclip\"},model:{value:(_vm.settings_icon),callback:function ($$v) {_vm.settings_icon=$$v},expression:\"settings_icon\"}})],1),_c('v-btn',{staticClass:\"settings__save-button mt-5\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.updateAccountIcon()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:save-16-filled\",\"height\":\"24px\"}}),_vm._v(\"アイコン画像を更新 \")],1),_c('v-form',{ref:\"settings_password\",staticClass:\"settings__item\",on:{\"submit\":function($event){$event.preventDefault();}}},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"新しいパスワード\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" KonomiTV アカウントの新しいパスワードを設定します。\"),_c('br')]),_c('v-text-field',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"placeholder\":\"新しいパスワード\",\"dense\":_vm.is_form_dense,\"type\":_vm.settings_password_showing ? 'text' : 'password',\"append-icon\":_vm.settings_password_showing ? 'mdi-eye' : 'mdi-eye-off',\"rules\":[_vm.settings_password_validation]},on:{\"click:append\":function($event){_vm.settings_password_showing = !_vm.settings_password_showing}},model:{value:(_vm.settings_password),callback:function ($$v) {_vm.settings_password=$$v},expression:\"settings_password\"}})],1),_c('v-btn',{staticClass:\"settings__save-button\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.updateAccountInfo('password')}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:save-16-filled\",\"height\":\"24px\"}}),_vm._v(\"パスワードを更新 \")],1),_c('v-divider',{staticClass:\"mt-6\"}),_c('div',{staticClass:\"settings__item mt-6\"},[_c('div',{staticClass:\"settings__item-heading error--text text--lighten-1\"},[_vm._v(\"アカウントを削除\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" 現在ログインしている KonomiTV アカウントを削除します。\"),_c('br'),_c('b',[_vm._v(\"アカウントに紐づくすべてのデータが削除されます。\")]),_vm._v(\"元に戻すことはできません。\"),_c('br')])]),_c('v-dialog',{attrs:{\"max-width\":\"385\"},scopedSlots:_vm._u([{key:\"activator\",fn:function({ on }){return [_c('v-btn',_vm._g({staticClass:\"settings__save-button error mt-5\",attrs:{\"depressed\":\"\"}},on),[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:delete-16-filled\",\"height\":\"24px\"}}),_vm._v(\"アカウントを削除 \")],1)]}}],null,false,1849668703),model:{value:(_vm.account_delete_confirm_dialog),callback:function ($$v) {_vm.account_delete_confirm_dialog=$$v},expression:\"account_delete_confirm_dialog\"}},[_c('v-card',[_c('v-card-title',{staticClass:\"justify-center pt-6 font-weight-bold\"},[_vm._v(\"本当にアカウントを削除しますか?\")]),_c('v-card-text',{staticClass:\"pt-2 pb-0\"},[_vm._v(\" アカウントに紐づくすべてのデータが削除されます。元に戻すことはできません。\"),_c('br'),_vm._v(\" 本当にアカウントを削除しますか? \")]),_c('v-card-actions',{staticClass:\"pt-4 px-6 pb-5\"},[_c('v-spacer'),_c('v-btn',{attrs:{\"color\":\"text\",\"text\":\"\"},on:{\"click\":function($event){_vm.account_delete_confirm_dialog = false}}},[_vm._v(\"キャンセル\")]),_c('v-btn',{attrs:{\"color\":\"error\"},on:{\"click\":function($event){return _vm.deleteAccount()}}},[_vm._v(\"削除\")])],1)],1)],1)],1):_vm._e()])])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"route-container\"},[_c('Header'),_c('main',[_c('Navigation'),_c('v-card',{staticClass:\"settings-container d-flex px-5 py-5 mx-auto background\",attrs:{\"elevation\":\"0\",\"width\":\"100%\",\"max-width\":\"1000\"}},[_c('div',[_c('v-navigation-drawer',{staticClass:\"settings-navigation flex-shrink-0 background\",attrs:{\"permanent\":\"\",\"width\":\"195\",\"height\":\"auto\"}},[_c('v-list-item',{staticClass:\"px-4\"},[_c('v-list-item-content',[_c('h1',[_vm._v(\"設定\")])])],1),_c('v-list',{staticClass:\"mt-2 px-0\",attrs:{\"nav\":\"\"}},[_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/general\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{staticStyle:{\"padding\":\"0 3px\"},attrs:{\"icon\":\"fa-solid:sliders-h\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"全般\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/caption\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:subtitles-16-filled\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"字幕\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/capture\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:image-multiple-16-filled\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"キャプチャ\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/account\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:person-20-filled\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"アカウント\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/jikkyo\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{staticStyle:{\"padding\":\"0 2px\"},attrs:{\"icon\":\"bi:chat-left-text-fill\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"ニコニコ実況\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/twitter\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{staticStyle:{\"padding\":\"0 1px\"},attrs:{\"icon\":\"fa-brands:twitter\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"Twitter\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/server\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:server-surface-16-filled\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"サーバー設定\")])],1)],1)],1)],1)],1),_c('v-card',{staticClass:\"settings ml-5 px-7 py-7 lighten-1\",attrs:{\"width\":\"100%\"}},[_vm._t(\"default\")],2)],1)],1)],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport Vue from 'vue';\n\nimport Header from '@/components/Header.vue';\nimport Navigation from '@/components/Navigation.vue';\n\n// 設定のベース画面なので、ロジックは基本置かない\nexport default Vue.extend({\n name: 'Settings-Base',\n components: {\n Header,\n Navigation,\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Base.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Base.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Base.vue?vue&type=template&id=d0f5a998&scoped=true&\"\nimport script from \"./Base.vue?vue&type=script&lang=ts&\"\nexport * from \"./Base.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Base.vue?vue&type=style&index=0&id=d0f5a998&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"d0f5a998\",\n null\n \n)\n\nexport default component.exports","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport Settings from '@/services/Settings';\nimport useSettingsStore from '@/store/SettingsStore';\nimport useUserStore from '@/store/UserStore';\nimport Utils from '@/utils';\nimport SettingsBase from '@/views/Settings/Base.vue';\n\nexport default Vue.extend({\n name: 'Settings-Account',\n components: {\n SettingsBase,\n },\n data() {\n return {\n\n // フォームを小さくするかどうか\n is_form_dense: Utils.isSmartphoneHorizontal(),\n\n // ローディング中かどうか\n is_loading: true,\n\n // ユーザー名とパスワード\n // ログイン画面やアカウント作成画面の data と同一のもの\n settings_username: null as string | null,\n settings_username_validation: (value: string | null) => {\n if (value === '' || value === null) return 'ユーザー名を入力してください。';\n if (/^.{2,}$/.test(value) === false) return 'ユーザー名は2文字以上で入力してください。';\n return true;\n },\n settings_password: null as string | null,\n settings_password_showing: false,\n settings_password_validation: (value: string | null) => {\n if (value === '' || value === null) return 'パスワードを入力してください。';\n // 正規表現の参考: https://qiita.com/grrrr/items/0b35b5c1c98eebfa5128\n if (/^[a-zA-Z0-9!-/:-@¥[-`{-~]{4,}$/.test(value) === false) return 'パスワードは4文字以上の半角英数記号を入力してください。';\n return true;\n },\n\n // アップロードするアイコン画像\n settings_icon: null as File | null,\n\n // アカウント削除確認ダイヤログ\n account_delete_confirm_dialog: false,\n\n // 設定を同期するかの設定値\n sync_settings: useSettingsStore().settings.sync_settings as boolean,\n\n // 設定を同期するときのダイヤログ\n sync_settings_dialog: false,\n };\n },\n computed: {\n // SettingsStore / UserStore に this.settingsStore / this.userStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useSettingsStore, useUserStore),\n },\n async created() {\n\n // アカウント情報を更新\n await this.userStore.fetchUser();\n\n // ローディング状態を解除\n this.is_loading = false;\n },\n watch: {\n // sync_settings の値の変更を監視する\n async sync_settings() {\n\n // 同期がオンになった & ダイヤログが表示されていない\n if (this.sync_settings === true && this.sync_settings_dialog === false) {\n\n // 同期対象の設定キーのみで設定データをまとめ直す\n const sync_settings = this.settingsStore.getSyncableClientSettings();\n\n // 同期対象のこのクライアントの設定を再度 JSON にする(文字列比較のため)\n const sync_settings_json = JSON.stringify(sync_settings);\n\n // サーバーから設定データをダウンロード\n // 一度オブジェクトに戻したものをを再度 JSON にする(文字列比較のため)\n const server_sync_settings = await Settings.fetchClientSettings();\n if (server_sync_settings === null) {\n this.$message.error('サーバーから設定データを取得できませんでした。');\n return;\n }\n const server_sync_settings_json = JSON.stringify(server_sync_settings);\n\n // このクライアントの設定とサーバーに保存されている設定が一致しない(=競合している)\n if (sync_settings_json !== server_sync_settings_json) {\n\n // 一度同期のスイッチをオフにして、クライアントとサーバーどちらの設定を使うのかを選択させるダイヤログを表示\n this.sync_settings_dialog = true;\n this.sync_settings = false;\n\n // このクライアントの設定とサーバーに保存されている設定が一致する\n } else {\n\n // 特に設定の同期をオンにしても問題ないので、そのまま有効にする\n this.settingsStore.settings.sync_settings = true;\n }\n\n // 同期がオフになった & ダイヤログが表示されていない\n } else if (this.sync_settings === false && this.sync_settings_dialog === false) {\n this.settingsStore.settings.sync_settings = false;\n }\n }\n },\n methods: {\n\n // このクライアントの設定でサーバー上の設定を上書きする\n async overrideServerSettingsFromClient() {\n\n // 強制的にこのクライアントの設定をサーバーに同期\n await this.settingsStore.syncClientSettingsToServer(true);\n\n // 設定の同期を有効化\n this.settingsStore.settings.sync_settings = true;\n this.sync_settings = true;\n\n // ダイヤログを閉じる\n this.sync_settings_dialog = false;\n },\n\n // サーバー上の設定でこのクライアントの設定を上書きする\n async overrideClientSettingsFromServer() {\n\n // 強制的にサーバーに保存されている設定データをこのクライアントに同期する\n // 設定の同期を有効化する前に実行しておくのが重要\n await this.settingsStore.syncClientSettingsFromServer(true);\n\n // 設定の同期を有効化\n // 値を変更した時点で設定データがサーバーにアップロードされてしまうので、\n // それよりも前に syncClientSettingsFromServer(true) でサーバー上の設定データを同期させておく必要がある\n // さもなければ、サーバー上の設定データがこのクライアントの設定で上書きされてしまい、overrideServerSettingsFromClient() と同じ挙動になってしまう\n this.settingsStore.settings.sync_settings = true;\n this.sync_settings = true;\n\n // ダイヤログを閉じる\n this.sync_settings_dialog = false;\n },\n\n async updateAccountInfo(update_type: 'username' | 'password') {\n\n // すべてのバリデーションが通過したときのみ\n // ref: https://qiita.com/Hijiri_Ishi/items/56cac99c8f3806a6fa24\n if (update_type === 'username') {\n if ((this.$refs.settings_username as any).validate() === false) return;\n } else {\n if ((this.$refs.settings_password as any).validate() === false) return;\n }\n\n // アカウント情報の更新処理 (エラーハンドリングを含む) を実行\n if (update_type === 'username') {\n if (this.settings_username === null) return;\n await this.userStore.updateUser({username: this.settings_username});\n } else {\n if (this.settings_password === null) return;\n await this.userStore.updateUser({password: this.settings_password});\n }\n },\n\n async updateAccountIcon() {\n\n // アイコン画像が選択されていないなら更新しない\n if (this.settings_icon === null) {\n this.$message.error('アップロードする画像を選択してください!');\n return;\n }\n\n // アイコン画像の更新処理 (エラーハンドリングを含む) を実行\n await this.userStore.updateUserIcon(this.settings_icon);\n },\n\n async deleteAccount() {\n\n // ダイヤログを閉じる\n this.account_delete_confirm_dialog = false;\n\n // アカウント削除処理 (エラーハンドリングを含む) を実行\n await this.userStore.deleteUser();\n }\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Account.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Account.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Account.vue?vue&type=template&id=7749b102&scoped=true&\"\nimport script from \"./Account.vue?vue&type=script&lang=ts&\"\nexport * from \"./Account.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Account.vue?vue&type=style&index=0&id=7749b102&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"7749b102\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('SettingsBase',[_c('h2',{staticClass:\"settings__heading\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"settings__back-button\",attrs:{\"to\":\"/settings/\"}},[_c('Icon',{attrs:{\"icon\":\"fluent:arrow-left-12-filled\",\"width\":\"25px\"}})],1),_c('Icon',{attrs:{\"icon\":\"fluent:subtitles-16-filled\",\"width\":\"25px\"}}),_c('span',{staticClass:\"ml-3\"},[_vm._v(\"字幕\")])],1),_c('div',{staticClass:\"settings__content\"},[_c('div',{staticClass:\"settings__item\"},[_c('label',{staticClass:\"settings__item-heading\"},[_vm._v(\"字幕のフォント\")]),_c('label',{staticClass:\"settings__item-label\"},[_vm._v(\" プレイヤーで字幕表示をオンにしているときの、字幕のフォントを設定します。\"),_c('br')]),_c('v-select',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"items\":_vm.caption_font},model:{value:(_vm.settingsStore.settings.caption_font),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"caption_font\", $$v)},expression:\"settingsStore.settings.caption_font\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"always_border_caption_text\"}},[_vm._v(\"字幕の文字を常に縁取りする\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"always_border_caption_text\"}},[_vm._v(\" 字幕表示時、縁取りをオンにすると、字幕が見やすくきれいになります。とくに理由がなければ、オンにしておくのがおすすめです。\"),_c('br'),_vm._v(\" この設定がオフのときも、字幕データ側で縁取りが指定されていれば、オンのときと同様に縁取り付きで描画されます。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"always_border_caption_text\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.always_border_caption_text),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"always_border_caption_text\", $$v)},expression:\"settingsStore.settings.always_border_caption_text\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"specify_caption_opacity\"}},[_vm._v(\"字幕の不透明度を指定する\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"specify_caption_opacity\"}},[_vm._v(\" 字幕表示時、不透明度を自分で指定するか設定できます。\"),_c('br'),_vm._v(\" この設定がオフのときは、字幕データ側で指定されている不透明度で描画します。とくに理由がなければ、オフにしておくのがおすすめです。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"specify_caption_opacity\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.specify_caption_opacity),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"specify_caption_opacity\", $$v)},expression:\"settingsStore.settings.specify_caption_opacity\"}})],1),_c('div',{staticClass:\"settings__item\",class:{'settings__item--disabled': _vm.settingsStore.settings.specify_caption_opacity === false}},[_c('label',{staticClass:\"settings__item-heading\"},[_vm._v(\"字幕の不透明度\")]),_c('label',{staticClass:\"settings__item-label\"},[_vm._v(\" 上の [字幕の不透明度を指定する] をオンに設定したときのみ有効です。不透明度を 0 に設定すれば、字幕の背景を非表示にできます。\"),_c('br')]),_c('div',{ref:\"caption_opacity\",staticClass:\"settings__item-label\"},[_c('v-slider',{staticClass:\"settings__item-form\",attrs:{\"ticks\":\"always\",\"thumb-label\":\"\",\"hide-details\":\"\",\"min\":0,\"max\":1,\"step\":0.05,\"disabled\":_vm.settingsStore.settings.specify_caption_opacity === false},model:{value:(_vm.settingsStore.settings.caption_opacity),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"caption_opacity\", $$v)},expression:\"settingsStore.settings.caption_opacity\"}})],1)]),_c('v-divider',{staticClass:\"mt-6\"}),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"tv_show_superimpose\"}},[_vm._v(\"テレビをみるときに文字スーパーを表示する\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"tv_show_superimpose\"}},[_vm._v(\" 文字スーパーは、緊急地震速報の赤テロップや、NHK BS のニュース速報のテロップなどで利用されています。とくに理由がなければ、オンにしておくのがおすすめです。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"tv_show_superimpose\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.tv_show_superimpose),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"tv_show_superimpose\", $$v)},expression:\"settingsStore.settings.tv_show_superimpose\"}})],1)],1)])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport useSettingsStore from '@/store/SettingsStore';\nimport Utils from '@/utils';\nimport SettingsBase from '@/views/Settings/Base.vue';\n\nexport default Vue.extend({\n name: 'Settings-Caption',\n components: {\n SettingsBase,\n },\n data() {\n return {\n\n // フォームを小さくするかどうか\n is_form_dense: Utils.isSmartphoneHorizontal(),\n\n // 字幕のフォントの選択肢\n caption_font: [\n {text: 'Windows TV ゴシック', value: 'Windows TV Gothic'},\n {text: 'Windows TV 丸ゴシック', value: 'Windows TV MaruGothic'},\n {text: 'Windows TV 太丸ゴシック', value: 'Windows TV FutoMaruGothic'},\n {text: 'ヒラギノTV丸ゴ', value: 'Hiragino TV Sans Rd S'},\n {text: '新丸ゴ ARIB', value: 'TT-ShinMGo-regular'},\n {text: 'Rounded M+ 1m for ARIB', value: 'Rounded M+ 1m for ARIB'},\n {text: 'Noto Sans JP', value: 'Noto Sans JP Caption'},\n {text: 'デフォルトのフォント', value: 'sans-serif'},\n ],\n };\n },\n computed: {\n // SettingsStore に this.settingsStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useSettingsStore),\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Caption.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Caption.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Caption.vue?vue&type=template&id=0642ebd7&\"\nimport script from \"./Caption.vue?vue&type=script&lang=ts&\"\nexport * from \"./Caption.vue?vue&type=script&lang=ts&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('SettingsBase',[_c('h2',{staticClass:\"settings__heading\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"settings__back-button\",attrs:{\"to\":\"/settings/\"}},[_c('Icon',{attrs:{\"icon\":\"fluent:arrow-left-12-filled\",\"width\":\"25px\"}})],1),_c('Icon',{attrs:{\"icon\":\"fluent:image-multiple-16-filled\",\"width\":\"26px\"}}),_c('span',{staticClass:\"ml-2\"},[_vm._v(\"キャプチャ\")])],1),_c('div',{staticClass:\"settings__content\"},[_c('div',{staticClass:\"settings__item settings__item--switch settings__item--sync-disabled\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"capture_copy_to_clipboard\"}},[_vm._v(\"キャプチャをクリップボードにコピーする\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"capture_copy_to_clipboard\"}},[_vm._v(\" この設定をオンにすると、撮ったキャプチャ画像がクリップボードにもコピーされます。\"),_c('br'),_vm._v(\" クリップボードの履歴をサポートしていない OS では、この設定をオンにしてキャプチャを撮ると、以前のクリップボードが上書きされます。注意してください。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"capture_copy_to_clipboard\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.capture_copy_to_clipboard),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"capture_copy_to_clipboard\", $$v)},expression:\"settingsStore.settings.capture_copy_to_clipboard\"}})],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"キャプチャの保存先\")]),_c('div',{staticClass:\"settings__item-label\"},[_c('p',[_vm._v(\" キャプチャした画像をブラウザでダウンロードするか、KonomiTV サーバーにアップロードするかを設定します。\"),_c('br'),_vm._v(\" ブラウザでのダウンロードと、KonomiTV サーバーへのアップロードを両方同時に行うこともできます。\"),_c('br')]),_c('p',[_vm._v(\" ブラウザでダウンロードすると、視聴中のデバイスのダウンロードフォルダに保存されます。\"),_c('br'),_vm._v(\" 視聴中のデバイスにそのまま保存されるためシンプルですが、保存先のフォルダを変更できないこと、PC 版 Chrome では毎回ダウンロードバーが表示されてしまったり、iOS Safari (PWA モード) ではダウンロードするとファイル概要画面が表示され再生が中断してしまったりなど、視聴に支障することがデメリットです (将来的には、iOS / Android アプリ版や拡張機能などで解消される予定) 。\"),_c('br')]),_c('p',[_vm._v(\" KonomiTV サーバーにアップロードすると、環境設定で指定されたキャプチャ保存フォルダに保存されます。視聴したデバイスにかかわらず、今までに撮ったキャプチャをひとつのフォルダにまとめて保存できます。\"),_c('br'),_vm._v(\" 他のデバイスでキャプチャを見るにはキャプチャ保存フォルダをネットワークに共有する必要があること、スマホ・タブレットではネットワーク上のフォルダへのアクセスがやや面倒なことがデメリットです。(将来的には、保存フォルダ内のキャプチャを Google フォトのように表示する機能を追加予定)\"),_c('br')])]),_c('v-select',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"items\":_vm.capture_save_mode},model:{value:(_vm.settingsStore.settings.capture_save_mode),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"capture_save_mode\", $$v)},expression:\"settingsStore.settings.capture_save_mode\"}})],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"字幕表示時のキャプチャの保存モード\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" 字幕表示時、キャプチャした画像に字幕を合成するかを設定します。\"),_c('br'),_vm._v(\" 映像のみのキャプチャと、字幕を合成したキャプチャを両方同時に保存することもできます。\"),_c('br'),_vm._v(\" なお、字幕非表示時は、常に映像のみ (+コメント付きキャプチャではコメントを合成して) 保存されます。\"),_c('br')]),_c('v-select',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"items\":_vm.capture_caption_mode},model:{value:(_vm.settingsStore.settings.capture_caption_mode),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"capture_caption_mode\", $$v)},expression:\"settingsStore.settings.capture_caption_mode\"}})],1)])])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport useSettingsStore from '@/store/SettingsStore';\nimport Utils from '@/utils';\nimport SettingsBase from '@/views/Settings/Base.vue';\n\nexport default Vue.extend({\n name: 'Settings-Capture',\n components: {\n SettingsBase,\n },\n data() {\n return {\n\n // フォームを小さくするかどうか\n is_form_dense: Utils.isSmartphoneHorizontal(),\n\n // キャプチャの保存先の選択肢\n capture_save_mode: [\n {text: 'ブラウザでダウンロード', value: 'Browser'},\n {text: 'KonomiTV サーバーにアップロード', value: 'UploadServer'},\n {text: 'ブラウザでのダウンロードと、KonomiTV サーバーへのアップロードを両方行う', value: 'Both'},\n ],\n\n // 字幕が表示されているときのキャプチャの保存モードの選択肢\n capture_caption_mode: [\n {text: '映像のみのキャプチャを保存する', value: 'VideoOnly'},\n {text: '字幕を合成したキャプチャを保存する', value: 'CompositingCaption'},\n {text: '映像のみのキャプチャと、字幕を合成したキャプチャを両方保存する', value: 'Both'},\n ],\n };\n },\n computed: {\n // SettingsStore に this.settingsStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useSettingsStore),\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Capture.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Capture.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Capture.vue?vue&type=template&id=053733a0&\"\nimport script from \"./Capture.vue?vue&type=script&lang=ts&\"\nexport * from \"./Capture.vue?vue&type=script&lang=ts&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('SettingsBase',[_c('h2',{staticClass:\"settings__heading\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"settings__back-button\",attrs:{\"to\":\"/settings/\"}},[_c('Icon',{attrs:{\"icon\":\"fluent:arrow-left-12-filled\",\"width\":\"25px\"}})],1),_c('Icon',{attrs:{\"icon\":\"fa-solid:sliders-h\",\"width\":\"19px\"}}),_c('span',{staticClass:\"ml-3\"},[_vm._v(\"全般\")])],1),_c('div',{staticClass:\"settings__content\"},[_c('div',{staticClass:\"settings__item settings__item--sync-disabled\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"テレビのデフォルトのストリーミング画質\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" テレビをライブストリーミングするときのデフォルトの画質を設定します。\"),_c('br'),_vm._v(\" ストリーミング画質はプレイヤーの設定からいつでも切り替えられます。\"),_c('br')]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" [1080p (60fps)] は、通常 30fps (60i) の映像を補間し、より滑らか(ぬるぬる)な映像で視聴できます!\"),_c('br'),_vm._v(\" [1080p (60fps)] で視聴するときは、環境設定の [利用するエンコーダー] をハードウェアエンコーダーに設定してください。FFmpeg (ソフトウェアエンコーダー) では、再生に支障が出ることがあります。\"),_c('br')]),_c('v-select',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"items\":_vm.tv_streaming_quality},model:{value:(_vm.settingsStore.settings.tv_streaming_quality),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"tv_streaming_quality\", $$v)},expression:\"settingsStore.settings.tv_streaming_quality\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch settings__item--sync-disabled\",class:{'settings__item--disabled': _vm.PlayerUtils.isHEVCVideoSupported() === false}},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"tv_data_saver_mode\"}},[_vm._v(\"テレビを通信節約モードで視聴する\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"tv_data_saver_mode\"}},[_vm._v(\" 通信節約モードでは、H.265 / HEVC という圧縮率の高いコーデックを使い、画質はほぼそのまま、通信量を通常の 1/2 程度に抑えながら視聴できます!\"),_c('br'),_vm._v(\" 通信節約モードで視聴するときは、環境設定の [利用するエンコーダー] をハードウェアエンコーダーに設定してください。FFmpeg (ソフトウェアエンコーダー) では、再生に支障が出る可能性が高いです。\"),_c('br'),(_vm.PlayerUtils.isHEVCVideoSupported() === false && _vm.Utils.isFirefox() === false)?_c('p',{staticClass:\"mt-1 mb-0 error--text lighten-1\"},[_vm._v(\" このデバイスでは通信節約モードがサポートされていません。 \")]):_vm._e(),(_vm.PlayerUtils.isHEVCVideoSupported() === false && _vm.Utils.isFirefox() === true)?_c('p',{staticClass:\"mt-1 mb-0 error--text lighten-1\"},[_vm._v(\" お使いの Firefox ブラウザでは通信節約モードがサポートされていません。 \")]):_vm._e()]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"tv_data_saver_mode\",\"inset\":\"\",\"hide-details\":\"\",\"disabled\":_vm.PlayerUtils.isHEVCVideoSupported() === false},model:{value:(_vm.settingsStore.settings.tv_data_saver_mode),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"tv_data_saver_mode\", $$v)},expression:\"settingsStore.settings.tv_data_saver_mode\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch settings__item--sync-disabled\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"tv_low_latency_mode\"}},[_vm._v(\"テレビを低遅延で視聴する\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"tv_low_latency_mode\"}},[_vm._v(\" 低遅延ストリーミングをオンにすると、\"),_c('b',[_vm._v(\"放送波との遅延を最短 0.9 秒に抑えて視聴できます!\")]),_c('br'),_vm._v(\" また、約 3 秒以上遅延したときに少しだけ再生速度を早める (1.1x) ことで、滑らかにストリーミングの遅延を取り戻します。\"),_c('br'),_vm._v(\" 宅外視聴などのネットワークが不安定になりがちな環境では、低遅延ストリーミングをオフにしてみると、映像のカクつきを改善できるかもしれません。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"tv_low_latency_mode\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.tv_low_latency_mode),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"tv_low_latency_mode\", $$v)},expression:\"settingsStore.settings.tv_low_latency_mode\"}})],1),_c('v-divider',{staticClass:\"mt-6\"}),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"デフォルトのパネルの表示状態\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" 視聴画面を開いたときに、右側のパネルをどう表示するかを設定します。\"),_c('br')]),_c('v-select',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"items\":_vm.panel_display_state},model:{value:(_vm.settingsStore.settings.panel_display_state),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"panel_display_state\", $$v)},expression:\"settingsStore.settings.panel_display_state\"}})],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"テレビをみるときにデフォルトで表示されるパネルのタブ\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" テレビの視聴画面を開いたときに、右側のパネルで最初に表示されるタブを設定します。\"),_c('br')]),_c('v-select',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"items\":_vm.tv_panel_active_tab},model:{value:(_vm.settingsStore.settings.tv_panel_active_tab),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"tv_panel_active_tab\", $$v)},expression:\"settingsStore.settings.tv_panel_active_tab\"}})],1),_c('v-divider',{staticClass:\"mt-6\"}),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"tv_show_superimpose\"}},[_vm._v(\"チャンネル選局のキーボードショートカットを \"+_vm._s(_vm.Utils.AltOrOption())+\" + 数字キー/テンキーに変更する\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"tv_show_superimpose\"}},[_vm._v(\" この設定をオンにすると、数字キーまたはテンキーに対応するリモコン番号(1~12)のチャンネルに切り替える際、\"+_vm._s(_vm.Utils.AltOrOption())+\" キーを同時に押す必要があります。\"),_c('br'),_vm._v(\" コメントやツイートを入力しようとして誤って数字キーを押してしまい、チャンネルが変わってしまう事態を避けたい方におすすめです。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"tv_show_superimpose\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.tv_channel_selection_requires_alt_key),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"tv_channel_selection_requires_alt_key\", $$v)},expression:\"settingsStore.settings.tv_channel_selection_requires_alt_key\"}})],1),_c('v-divider',{staticClass:\"mt-6\"}),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"設定をエクスポート\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" このデバイス(ブラウザ)に保存されている設定データを、エクスポート(ダウンロード)できます。\"),_c('br'),_vm._v(\" ダウンロードした設定データ (KonomiTV-Settings.json) は、[設定をインポート] からインポートできます。異なるサーバーの KonomiTV を同じ設定で使いたいときなどに使ってください。\"),_c('br')])]),_c('v-btn',{staticClass:\"settings__save-button mt-4\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.exportSettings()}}},[_c('Icon',{staticClass:\"mr-3\",attrs:{\"icon\":\"fa6-solid:download\",\"height\":\"19px\"}}),_vm._v(\"設定をエクスポート \")],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading error--text text--lighten-1\"},[_vm._v(\"設定をインポート\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" [設定をエクスポート] でダウンロードした設定データを、このデバイス(ブラウザ)にインポートできます。\"),_c('br'),_vm._v(\" 設定をインポートすると、\"),_c('b',[_vm._v(\"現在のデバイス設定はすべて上書きされます。\")]),_vm._v(\"元に戻すことはできません。\"),_c('br'),_vm._v(\" 設定のデバイス間同期がオンのときは、\"),_c('b',[_vm._v(\"同期が有効なすべてのデバイスに反映されます。\")]),_vm._v(\"十分ご注意ください。\"),_c('br')]),_c('v-file-input',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"placeholder\":\"設定データ (KonomiTV-Settings.json) を選択\",\"dense\":_vm.is_form_dense,\"accept\":\"application/json\",\"prepend-icon\":\"\",\"prepend-inner-icon\":\"mdi-paperclip\"},model:{value:(_vm.import_settings_file),callback:function ($$v) {_vm.import_settings_file=$$v},expression:\"import_settings_file\"}})],1),_c('v-btn',{staticClass:\"settings__save-button error mt-5\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.importSettings()}}},[_c('Icon',{staticClass:\"mr-3\",attrs:{\"icon\":\"fa6-solid:upload\",\"height\":\"19px\"}}),_vm._v(\"設定をインポート \")],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading error--text text--lighten-1\"},[_vm._v(\"設定を初期状態にリセット\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" このデバイス(ブラウザ)に保存されている設定データを、初期状態のデフォルト値にリセットできます。\"),_c('br'),_vm._v(\" 設定をリセットすると、元に戻すことはできません。\"),_c('br'),_vm._v(\" 設定のデバイス間同期がオンのときは、\"),_c('b',[_vm._v(\"同期が有効なすべてのデバイスに反映されます。\")]),_vm._v(\"十分ご注意ください。\"),_c('br')])]),_c('v-btn',{staticClass:\"settings__save-button error mt-5\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.resetSettings()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"material-symbols:device-reset-rounded\",\"height\":\"23px\"}}),_vm._v(\"設定をリセット \")],1)],1)])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport useSettingsStore from '@/store/SettingsStore';\nimport Utils, { PlayerUtils } from '@/utils';\nimport SettingsBase from '@/views/Settings/Base.vue';\n\nconst QUALITY_H264 = [\n {text: '1080p (60fps) (約4.50GB/h / 平均10.0Mbps)', value: '1080p-60fps'},\n {text: '1080p (約4.50GB/h / 平均10.0Mbps)', value: '1080p'},\n {text: '810p (約2.62GB/h / 平均5.8Mbps)', value: '810p'},\n {text: '720p (約2.18GB/h / 平均4.9Mbps)', value: '720p'},\n {text: '540p (約1.52GB/h / 平均3.4Mbps)', value: '540p'},\n {text: '480p (約1.06GB/h / 平均2.3Mbps)', value: '480p'},\n {text: '360p (約0.60GB/h / 平均1.3Mbps)', value: '360p'},\n {text: '240p (約0.35GB/h / 平均0.8Mbps)', value: '240p'},\n];\n\nconst QUALITY_H265 = [\n {text: '1080p (60fps) (約1.80GB/h / 平均4.0Mbps)', value: '1080p-60fps'},\n {text: '1080p (約1.37GB/h / 平均3.0Mbps)', value: '1080p'},\n {text: '810p (約1.05GB/h / 平均2.3Mbps)', value: '810p'},\n {text: '720p (約0.82GB/h / 平均1.8Mbps)', value: '720p'},\n {text: '540p (約0.53GB/h / 平均1.2Mbps)', value: '540p'},\n {text: '480p (約0.46GB/h / 平均1.0Mbps)', value: '480p'},\n {text: '360p (約0.30GB/h / 平均0.7Mbps)', value: '360p'},\n {text: '240p (約0.20GB/h / 平均0.4Mbps)', value: '240p'},\n];\n\nexport default Vue.extend({\n name: 'Settings-General',\n components: {\n SettingsBase,\n },\n data() {\n return {\n\n // ユーティリティをテンプレートで使えるように\n Utils: Utils,\n PlayerUtils: PlayerUtils,\n\n // フォームを小さくするかどうか\n is_form_dense: Utils.isSmartphoneHorizontal(),\n\n // テレビのデフォルトのストリーミング画質の選択肢\n tv_streaming_quality: QUALITY_H264,\n\n // デフォルトのパネルの表示状態の選択肢\n panel_display_state: [\n {text: '前回の状態を復元する', value: 'RestorePreviousState'},\n {text: '常に表示する', value: 'AlwaysDisplay'},\n {text: '常に折りたたむ', value: 'AlwaysFold'},\n ],\n\n // テレビをみるときにデフォルトで表示されるパネルのタブの選択肢\n tv_panel_active_tab: [\n {text: '番組情報タブ', value: 'Program'},\n {text: 'チャンネルタブ', value: 'Channel'},\n {text: 'コメントタブ', value: 'Comment'},\n {text: 'Twitter タブ', value: 'Twitter'},\n ],\n\n // 選択された設定データ (KonomiTV-Settings.json) が入る\n import_settings_file: null as File | null,\n };\n },\n computed: {\n // SettingsStore に this.settingsStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useSettingsStore),\n },\n created() {\n if (this.settingsStore.settings.tv_data_saver_mode === true) {\n this.tv_streaming_quality = QUALITY_H265;\n }\n },\n watch: {\n 'settingsStore.settings.tv_data_saver_mode': {\n immediate: true,\n handler(val: boolean) {\n if (val === true) {\n this.tv_streaming_quality = QUALITY_H265;\n } else {\n this.tv_streaming_quality = QUALITY_H264;\n }\n },\n }\n },\n methods: {\n\n // 設定データをエクスポートする\n exportSettings() {\n\n // 設定データを JSON 化して取得\n const settings_json = JSON.stringify(this.settingsStore.settings, null, 4);\n\n // ダウンロードさせるために一旦 Blob にしてから、KonomiTV-Settings.json としてダウンロード\n const settings_json_blob = new Blob([settings_json], {type: 'application/json'});\n Utils.downloadBlobData(settings_json_blob, 'KonomiTV-Settings.json');\n this.$message.success('設定をエクスポートしました。');\n },\n\n // 設定データをインポートする\n async importSettings() {\n\n // 設定データが選択されていないときは実行しない\n if (this.import_settings_file === null) {\n this.$message.error('インポートする設定データを選択してください!');\n return;\n }\n\n // 設定データのインポートを実行\n const result = await this.settingsStore.importClientSettings(this.import_settings_file);\n if (result === true) {\n this.$message.success('設定をインポートしました。');\n window.setTimeout(() => this.$router.go(0), 300);\n } else {\n this.$message.error('設定データが不正なため、インポートできませんでした。');\n }\n },\n\n // 設定データをリセットする\n async resetSettings() {\n await this.settingsStore.resetClientSettings();\n this.$message.success('設定をリセットしました。');\n window.setTimeout(() => this.$router.go(0), 300);\n },\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./General.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./General.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./General.vue?vue&type=template&id=24ae53a3&\"\nimport script from \"./General.vue?vue&type=script&lang=ts&\"\nexport * from \"./General.vue?vue&type=script&lang=ts&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"route-container\"},[_c('Header'),_c('main',[_c('Navigation'),_c('v-card',{staticClass:\"settings-container d-flex px-5 py-5 mx-auto background\",attrs:{\"elevation\":\"0\",\"width\":\"100%\",\"max-width\":\"1000\"}},[_c('v-navigation-drawer',{staticClass:\"settings-navigation flex-shrink-0 background\",attrs:{\"permanent\":\"\",\"width\":\"100%\",\"height\":\"auto\"}},[_c('v-list-item',{staticClass:\"px-1\"},[_c('v-list-item-content',[_c('h1',[_vm._v(\"設定\")])])],1),_c('v-list',{staticClass:\"mt-2 px-0\",attrs:{\"nav\":\"\"}},[_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/general\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{staticStyle:{\"padding\":\"0 3px\"},attrs:{\"icon\":\"fa-solid:sliders-h\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"全般\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/caption\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:subtitles-16-filled\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"字幕\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/capture\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:image-multiple-16-filled\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"キャプチャ\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/account\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:person-20-filled\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"アカウント\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/jikkyo\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{staticStyle:{\"padding\":\"0 2px\"},attrs:{\"icon\":\"bi:chat-left-text-fill\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"ニコニコ実況\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/twitter\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{staticStyle:{\"padding\":\"0 1px\"},attrs:{\"icon\":\"fa-brands:twitter\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"Twitter\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/server\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:server-surface-16-filled\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"サーバー設定\")])],1)],1),_c('v-list-item',{staticClass:\"px-4 settings-navigation-version\",class:{'settings-navigation-version--highlight': _vm.versionStore.is_update_available},attrs:{\"link\":\"\",\"color\":\"primary\",\"href\":\"https://github.com/tsukumijima/KonomiTV\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:info-16-regular\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\" version \"+_vm._s(_vm.versionStore.client_version)+_vm._s(_vm.versionStore.is_update_available ? ' (Update Available)' : '')+\" \")])],1)],1)],1)],1)],1)],1)],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport Header from '@/components/Header.vue';\nimport Navigation from '@/components/Navigation.vue';\nimport useVersionStore from '@/store/VersionStore';\n\nexport default Vue.extend({\n name: 'Settings-Index',\n components: {\n Header,\n Navigation,\n },\n computed: {\n ...mapStores(useVersionStore),\n },\n async created() {\n await this.versionStore.fetchServerVersion();\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Index.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Index.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Index.vue?vue&type=template&id=48d089f3&scoped=true&\"\nimport script from \"./Index.vue?vue&type=script&lang=ts&\"\nexport * from \"./Index.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Index.vue?vue&type=style&index=0&id=48d089f3&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"48d089f3\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('SettingsBase',[_c('h2',{staticClass:\"settings__heading\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"settings__back-button\",attrs:{\"to\":\"/settings/\"}},[_c('Icon',{attrs:{\"icon\":\"fluent:arrow-left-12-filled\",\"width\":\"25px\"}})],1),_c('Icon',{attrs:{\"icon\":\"bi:chat-left-text-fill\",\"width\":\"19px\"}}),_c('span',{staticClass:\"ml-3\"},[_vm._v(\"ニコニコ実況\")])],1),_c('div',{staticClass:\"settings__content\",class:{'settings__content--loading': _vm.is_loading}},[(_vm.userStore.user === null || _vm.userStore.user.niconico_user_id === null)?_c('div',{staticClass:\"niconico-account niconico-account--anonymous\"},[_c('div',{staticClass:\"niconico-account-wrapper\"},[_c('Icon',{staticClass:\"flex-shrink-0\",attrs:{\"icon\":\"bi:chat-left-text-fill\",\"width\":\"45px\"}}),_c('div',{staticClass:\"niconico-account__info ml-4\"},[_c('div',{staticClass:\"niconico-account__info-name\"},[_c('span',{staticClass:\"niconico-account__info-name-text\"},[_vm._v(\"ニコニコアカウントと連携していません\")])]),_c('span',{staticClass:\"niconico-account__info-description\"},[_vm._v(\" ニコニコアカウントと連携すると、テレビを見ながらニコニコ実況にコメントできるようになります。 \")])])],1),_c('v-btn',{staticClass:\"niconico-account__login ml-auto\",attrs:{\"color\":\"secondary\",\"width\":\"130\",\"height\":\"56\",\"depressed\":\"\"},on:{\"click\":function($event){return _vm.loginNiconicoAccount()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:plug-connected-20-filled\",\"height\":\"26\"}}),_vm._v(\"連携する \")],1)],1):_vm._e(),(_vm.userStore.user !== null && _vm.userStore.user.niconico_user_id !== null)?_c('div',{staticClass:\"niconico-account\"},[_c('div',{staticClass:\"niconico-account-wrapper\"},[_c('img',{staticClass:\"niconico-account__icon\",attrs:{\"src\":_vm.userStore.user_niconico_icon_url}}),_c('div',{staticClass:\"niconico-account__info\"},[_c('div',{staticClass:\"niconico-account__info-name\"},[_c('span',{staticClass:\"niconico-account__info-name-text\"},[_vm._v(_vm._s(_vm.userStore.user.niconico_user_name)+\" と連携しています\")])]),_c('span',{staticClass:\"niconico-account__info-description\"},[_c('span',{staticClass:\"mr-2\",staticStyle:{\"white-space\":\"nowrap\"}},[_vm._v(\"Niconico User ID:\")]),_c('a',{staticClass:\"mr-2\",attrs:{\"href\":`https://www.nicovideo.jp/user/${_vm.userStore.user.niconico_user_id}`,\"target\":\"_blank\"}},[_vm._v(_vm._s(_vm.userStore.user.niconico_user_id))]),(_vm.userStore.user.niconico_user_premium == true)?_c('span',{staticClass:\"secondary--text\"},[_vm._v(\"(Premium)\")]):_vm._e()])])]),_c('v-btn',{staticClass:\"niconico-account__login ml-auto\",attrs:{\"color\":\"secondary\",\"width\":\"130\",\"height\":\"56\",\"depressed\":\"\"},on:{\"click\":function($event){return _vm.logoutNiconicoAccount()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:plug-disconnected-20-filled\",\"height\":\"26\"}}),_vm._v(\"連携解除 \")],1)],1):_vm._e(),_c('div',{staticClass:\"settings__item mt-7\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"コメントのミュート設定\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" 表示したくないコメントを、映像上やコメントリストに表示しないようにミュートできます。\"),_c('br')])]),_c('v-btn',{staticClass:\"settings__save-button mt-4\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){_vm.comment_mute_settings_modal = !_vm.comment_mute_settings_modal}}},[_c('Icon',{attrs:{\"icon\":\"heroicons-solid:filter\",\"height\":\"19px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(\"コメントのミュート設定を開く\")])],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"コメントの速さ\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" プレイヤーに流れるコメントの速さを設定します。\"),_c('br'),_vm._v(\" たとえば 1.2 に設定すると、コメントが 1.2 倍速く流れます。\"),_c('br')]),_c('v-slider',{staticClass:\"settings__item-form\",attrs:{\"ticks\":\"always\",\"thumb-label\":\"\",\"hide-details\":\"\",\"step\":0.1,\"min\":0.5,\"max\":2},model:{value:(_vm.settingsStore.settings.comment_speed_rate),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"comment_speed_rate\", $$v)},expression:\"settingsStore.settings.comment_speed_rate\"}})],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"コメントの文字サイズ\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" プレイヤーに流れるコメントの文字サイズの基準値を設定します。\"),_c('br'),_vm._v(\" 実際の文字サイズは画面サイズに合わせて調整されます。デフォルトの文字サイズは 34px です。\"),_c('br')]),_c('v-slider',{staticClass:\"settings__item-form\",attrs:{\"ticks\":\"always\",\"thumb-label\":\"\",\"hide-details\":\"\",\"min\":20,\"max\":60},model:{value:(_vm.settingsStore.settings.comment_font_size),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"comment_font_size\", $$v)},expression:\"settingsStore.settings.comment_font_size\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"close_comment_form_after_sending\"}},[_vm._v(\"コメント送信後にコメント入力フォームを閉じる\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"close_comment_form_after_sending\"}},[_vm._v(\" この設定をオンにすると、コメントを送信した後に、コメント入力フォームが自動で閉じるようになります。\"),_c('br'),_vm._v(\" コメント入力フォームが表示されたままだと、大半のショートカットキーが文字入力と競合して使えなくなります。とくに理由がなければ、オンにしておくのがおすすめです。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"close_comment_form_after_sending\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.close_comment_form_after_sending),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"close_comment_form_after_sending\", $$v)},expression:\"settingsStore.settings.close_comment_form_after_sending\"}})],1)],1),_c('CommentMuteSettings',{model:{value:(_vm.comment_mute_settings_modal),callback:function ($$v) {_vm.comment_mute_settings_modal=$$v},expression:\"comment_mute_settings_modal\"}})],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('v-dialog',{attrs:{\"max-width\":\"770\",\"transition\":\"slide-y-transition\"},model:{value:(_vm.comment_mute_settings_modal),callback:function ($$v) {_vm.comment_mute_settings_modal=$$v},expression:\"comment_mute_settings_modal\"}},[_c('v-card',{staticClass:\"comment-mute-settings\"},[_c('v-card-title',{staticClass:\"px-5 pt-5 pb-3 d-flex align-center font-weight-bold\",staticStyle:{\"height\":\"60px\"}},[_c('Icon',{attrs:{\"icon\":\"heroicons-solid:filter\",\"height\":\"26px\"}}),_c('span',{staticClass:\"ml-3\"},[_vm._v(\"コメントのミュート設定\")]),_c('v-spacer'),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"d-flex align-center rounded-circle cursor-pointer px-2 py-2\",on:{\"click\":function($event){_vm.comment_mute_settings_modal = false}}},[_c('Icon',{attrs:{\"icon\":\"fluent:dismiss-12-filled\",\"width\":\"23px\",\"height\":\"23px\"}})],1)],1),_c('div',{staticClass:\"px-5 pb-5\"},[_c('div',{staticClass:\"text-subtitle-1 d-flex align-center font-weight-bold mt-4\"},[_c('Icon',{attrs:{\"icon\":\"fa-solid:sliders-h\",\"width\":\"24px\",\"height\":\"20px\"}}),_c('span',{staticClass:\"ml-2\"},[_vm._v(\"クイック設定\")])],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"mute_vulgar_comments\"}},[_vm._v(\" 露骨な表現を含むコメントをミュートする \")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"mute_vulgar_comments\"}},[_vm._v(\" 性的な単語などの露骨・下品な表現を含むコメントを、一括でミュートするかを設定します。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"mute_vulgar_comments\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.mute_vulgar_comments),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"mute_vulgar_comments\", $$v)},expression:\"settingsStore.settings.mute_vulgar_comments\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"mute_abusive_discriminatory_prejudiced_comments\"}},[_vm._v(\" ネガティブな表現、差別的な表現、政治的に偏った表現を含むコメントをミュートする \")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"mute_abusive_discriminatory_prejudiced_comments\"}},[_vm._v(\" 『死ね』『殺す』などのネガティブな表現、特定の国や人々への差別的な表現、政治的に偏った表現を含むコメントを、一括でミュートするかを設定します。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"mute_abusive_discriminatory_prejudiced_comments\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.mute_abusive_discriminatory_prejudiced_comments),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"mute_abusive_discriminatory_prejudiced_comments\", $$v)},expression:\"settingsStore.settings.mute_abusive_discriminatory_prejudiced_comments\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"mute_big_size_comments\"}},[_vm._v(\" 文字サイズが大きいコメントをミュートする \")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"mute_big_size_comments\"}},[_vm._v(\" 通常より大きい文字サイズで表示されるコメントを、一括でミュートするかを設定します。\"),_c('br'),_vm._v(\" 文字サイズが大きいコメントには迷惑なコメントが多いです。基本的にはオンにしておくのがおすすめです。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"mute_big_size_comments\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.mute_big_size_comments),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"mute_big_size_comments\", $$v)},expression:\"settingsStore.settings.mute_big_size_comments\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"mute_fixed_comments\"}},[_vm._v(\" 映像の上下に固定表示されるコメントをミュートする \")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"mute_fixed_comments\"}},[_vm._v(\" 映像の上下に固定された状態で表示されるコメントを、一括でミュートするかを設定します。\"),_c('br'),_vm._v(\" 固定表示されるコメントが煩わしい方におすすめです。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"mute_fixed_comments\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.mute_fixed_comments),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"mute_fixed_comments\", $$v)},expression:\"settingsStore.settings.mute_fixed_comments\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"mute_colored_comments\"}},[_vm._v(\" 色付きのコメントをミュートする \")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"mute_colored_comments\"}},[_vm._v(\" 白以外の色で表示される色付きのコメントを、一括でミュートするかを設定します。\"),_c('br'),_vm._v(\" この設定をオンにしておくと、目立つ色のコメントを一掃できます。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"mute_colored_comments\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.mute_colored_comments),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"mute_colored_comments\", $$v)},expression:\"settingsStore.settings.mute_colored_comments\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"mute_consecutive_same_characters_comments\"}},[_vm._v(\" 8文字以上同じ文字が連続しているコメントをミュートする \")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"mute_consecutive_same_characters_comments\"}},[_vm._v(\" 『wwwwwwwwwww』『あばばばばばばばばば』など、8文字以上同じ文字が連続しているコメントを、一括でミュートするかを設定します。\"),_c('br'),_vm._v(\" しばしばあるテンプレコメントが煩わしい方におすすめです。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"mute_consecutive_same_characters_comments\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.mute_consecutive_same_characters_comments),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"mute_consecutive_same_characters_comments\", $$v)},expression:\"settingsStore.settings.mute_consecutive_same_characters_comments\"}})],1),_c('div',{staticClass:\"text-subtitle-1 d-flex align-center font-weight-bold mt-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:comment-dismiss-20-filled\",\"width\":\"24px\"}}),_c('span',{staticClass:\"ml-2 mr-2\"},[_vm._v(\"ミュート済みのキーワード\")]),_c('v-btn',{staticClass:\"ml-auto\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.settingsStore.settings.muted_comment_keywords.push({match: 'partial', pattern: ''})}}},[_c('Icon',{attrs:{\"icon\":\"fluent:add-12-filled\",\"height\":\"17px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(\"追加\")])],1)],1),_c('div',{staticClass:\"muted-comment-items\"},_vm._l((_vm.settingsStore.settings.muted_comment_keywords),function(muted_comment_keyword,index){return _c('div',{key:index,staticClass:\"muted-comment-item\"},[_c('v-text-field',{staticClass:\"muted-comment-item__input\",attrs:{\"type\":\"search\",\"dense\":\"\",\"outlined\":\"\",\"hide-details\":\"\",\"placeholder\":\"ミュートするキーワードを入力\"},model:{value:(_vm.settingsStore.settings.muted_comment_keywords[index].pattern),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings.muted_comment_keywords[index], \"pattern\", $$v)},expression:\"settingsStore.settings.muted_comment_keywords[index].pattern\"}}),_c('v-select',{staticClass:\"muted-comment-item__match-type\",attrs:{\"dense\":\"\",\"outlined\":\"\",\"hide-details\":\"\",\"items\":_vm.muted_comment_keyword_match_type},model:{value:(_vm.settingsStore.settings.muted_comment_keywords[index].match),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings.muted_comment_keywords[index], \"match\", $$v)},expression:\"settingsStore.settings.muted_comment_keywords[index].match\"}}),_c('button',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"muted-comment-item__delete-button\",on:{\"click\":function($event){_vm.settingsStore.settings.muted_comment_keywords\n .splice(_vm.settingsStore.settings.muted_comment_keywords.indexOf(muted_comment_keyword), 1)}}},[_c('Icon',{attrs:{\"icon\":\"fluent:delete-16-filled\",\"width\":\"20px\"}})],1)],1)}),0),_c('div',{staticClass:\"text-subtitle-1 d-flex align-center font-weight-bold mt-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:person-prohibited-20-filled\",\"width\":\"24px\"}}),_c('span',{staticClass:\"ml-2 mr-2\"},[_vm._v(\"ミュート済みのニコニコユーザー ID\")]),_c('v-btn',{staticClass:\"ml-auto\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.settingsStore.settings.muted_niconico_user_ids.push('')}}},[_c('Icon',{attrs:{\"icon\":\"fluent:add-12-filled\",\"height\":\"17px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(\"追加\")])],1)],1),_c('div',{staticClass:\"muted-comment-items\"},_vm._l((_vm.settingsStore.settings.muted_niconico_user_ids),function(muted_niconico_user_id,index){return _c('div',{key:index,staticClass:\"muted-comment-item\"},[_c('v-text-field',{staticClass:\"muted-comment-item__input\",attrs:{\"type\":\"search\",\"dense\":\"\",\"outlined\":\"\",\"hide-details\":\"\",\"placeholder\":\"ミュートするニコニコユーザー ID を入力\"},model:{value:(_vm.settingsStore.settings.muted_niconico_user_ids[index]),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings.muted_niconico_user_ids, index, $$v)},expression:\"settingsStore.settings.muted_niconico_user_ids[index]\"}}),_c('button',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"muted-comment-item__delete-button\",on:{\"click\":function($event){_vm.settingsStore.settings.muted_niconico_user_ids\n .splice(_vm.settingsStore.settings.muted_niconico_user_ids.indexOf(muted_niconico_user_id), 1)}}},[_c('Icon',{attrs:{\"icon\":\"fluent:delete-16-filled\",\"width\":\"20px\"}})],1)],1)}),0)])],1)],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport { mapStores } from 'pinia';\nimport Vue, { PropType } from 'vue';\n\nimport useSettingsStore from '@/store/SettingsStore';\n\nexport default Vue.extend({\n name: 'CommentMuteSettings',\n // カスタム v-model を実装する\n // ref: https://jp.vuejs.org/v2/guide/components-custom-events.html\n model: {\n prop: 'showing', // v-model で渡された値が \"showing\" props に入る\n event: 'change', // \"change\" イベントで親コンポーネントに反映\n },\n props: {\n // コメントのミュート設定のモーダルを表示するか\n showing: {\n type: Boolean as PropType,\n required: true,\n }\n },\n data() {\n return {\n\n // インターバルのタイマー ID\n interval_timer_id: 0,\n\n // コメントのミュート設定のモーダルを表示するか\n comment_mute_settings_modal: false,\n\n // ミュート済みのキーワードのマッチタイプ\n muted_comment_keyword_match_type: [\n {text: '部分一致', value: 'partial'},\n {text: '前方一致', value: 'forward'},\n {text: '後方一致', value: 'backward'},\n {text: '完全一致', value: 'exact'},\n {text: '正規表現', value: 'regex'},\n ],\n };\n },\n computed: {\n // SettingsStore に this.settingsStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useSettingsStore),\n },\n watch: {\n\n // showing (親コンポーネント側) の変更を監視し、変更されたら comment_mute_settings_modal に反映する\n showing() {\n this.comment_mute_settings_modal = this.showing as boolean;\n },\n\n // comment_mute_settings_modal (子コンポーネント側) の変更を監視し、変更されたら this.$emit() で親コンポーネントに伝える\n comment_mute_settings_modal() {\n this.$emit('change', this.comment_mute_settings_modal);\n }\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./CommentMuteSettings.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./CommentMuteSettings.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./CommentMuteSettings.vue?vue&type=template&id=2cd59ba0&scoped=true&\"\nimport script from \"./CommentMuteSettings.vue?vue&type=script&lang=ts&\"\nexport * from \"./CommentMuteSettings.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./CommentMuteSettings.vue?vue&type=style&index=0&id=2cd59ba0&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"2cd59ba0\",\n null\n \n)\n\nexport default component.exports","\nimport APIClient from '@/services/APIClient';\n\n\n/** ニコニコアカウントと連携するための認証 URL を表すインターフェイス */\nexport interface INiconicoAuthURL {\n authorization_url: string;\n}\n\n\nclass Niconico {\n\n /**\n * ニコニコアカウントと連携するための認証 URL を取得する\n * @returns 認証 URL or 認証 URL の取得に失敗した場合は null\n */\n static async fetchAuthorizationURL(): Promise {\n\n // API リクエストを実行\n const response = await APIClient.get('/niconico/auth');\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'ニコニコアカウントとの連携用の認証 URL を取得できませんでした。');\n return null;\n }\n\n return response.data.authorization_url;\n }\n\n\n /**\n * 現在ログイン中のユーザーアカウントに紐づくニコニコアカウントとの連携を解除する\n * @returns 連携解除に成功した場合は true, 失敗した場合は false\n */\n static async logoutAccount(): Promise {\n\n // API リクエストを実行\n const response = await APIClient.delete('/niconico/logout');\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'ニコニコアカウントとの連携を解除できませんでした。');\n return false;\n }\n\n return true;\n }\n}\n\nexport default Niconico;\n","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport CommentMuteSettings from '@/components/Settings/CommentMuteSettings.vue';\nimport Niconico from '@/services/Niconico';\nimport useSettingsStore from '@/store/SettingsStore';\nimport useUserStore from '@/store/UserStore';\nimport Utils from '@/utils';\nimport SettingsBase from '@/views/Settings/Base.vue';\n\nexport default Vue.extend({\n name: 'Settings-Jikkyo',\n components: {\n SettingsBase,\n CommentMuteSettings,\n },\n data() {\n return {\n\n // コメントのミュート設定のモーダルを表示するか\n comment_mute_settings_modal: false,\n\n // ローディング中かどうか\n is_loading: true,\n };\n },\n computed: {\n // SettingsStore / UserStore に this.settingsStore / this.userStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useSettingsStore, useUserStore),\n },\n async created() {\n\n // アカウント情報を更新\n await this.userStore.fetchUser();\n\n // ローディング状態を解除\n this.is_loading = false;\n\n // もしハッシュ (# から始まるフラグメント) に何か指定されていたら、\n // OAuth 連携のコールバックの結果が入っている可能性が高いので、パースを試みる\n // アカウント情報更新より後にしないと Snackbar がうまく表示されない\n if (location.hash !== '') {\n const params = new URLSearchParams(location.hash.replace('#', ''));\n if (params.get('status') !== null && params.get('detail') !== null) {\n // コールバックの結果を取得できたので、OAuth 連携の結果を画面に通知する\n const authorization_status = parseInt(params.get('status')!);\n const authorization_detail = params.get('detail')!;\n this.onOAuthCallbackReceived(authorization_status, authorization_detail);\n // URL からフラグメントを削除\n // ref: https://stackoverflow.com/a/49373716/17124142\n history.replaceState(null, '', ' ');\n }\n }\n },\n methods: {\n async loginNiconicoAccount() {\n\n // ログインしていない場合はエラーにする\n if (this.userStore.is_logged_in === false) {\n this.$message.warning('連携をはじめるには、KonomiTV アカウントにログインしてください。');\n return;\n }\n\n // ニコニコアカウントと連携するための認証 URL を取得\n const authorization_url = await Niconico.fetchAuthorizationURL();\n if (authorization_url === null) {\n return;\n }\n\n // モバイルデバイスではポップアップが事実上使えない (特に Safari ではブロックされてしまう) ので、素直にリダイレクトで実装する\n if (Utils.isMobileDevice() === true) {\n location.href = authorization_url;\n return;\n }\n\n // OAuth 連携のため、認証 URL をポップアップウインドウで開く\n // window.open() の第2引数はユニークなものにしておくと良いらしい\n // ref: https://qiita.com/catatsuy/items/babce8726ea78f5d25b1 (大変参考になりました)\n const popup_window = window.open(authorization_url, 'KonomiTV-OAuthPopup', Utils.getWindowFeatures());\n if (popup_window === null) {\n this.$message.error('ポップアップウインドウを開けませんでした。');\n return;\n }\n\n // 認証完了 or 失敗後、ポップアップウインドウから送信される文字列を受信\n const onMessage = async (event) => {\n\n // すでにウインドウが閉じている場合は実行しない\n if (popup_window.closed) return;\n\n // 受け取ったオブジェクトに KonomiTV-OAuthPopup キーがない or そもそもオブジェクトではない際は実行しない\n // ブラウザの拡張機能から結構余計な message が飛んでくるっぽい…。\n if (Utils.typeof(event.data) !== 'object') return;\n if (('KonomiTV-OAuthPopup' in event.data) === false) return;\n\n // 認証は完了したので、ポップアップウインドウを閉じ、リスナーを解除する\n if (popup_window) popup_window.close();\n window.removeEventListener('message', onMessage);\n\n // ステータスコードと詳細メッセージを取得\n const authorization_status = event.data['KonomiTV-OAuthPopup']['status'] as number;\n const authorization_detail = event.data['KonomiTV-OAuthPopup']['detail'] as string;\n this.onOAuthCallbackReceived(authorization_status, authorization_detail);\n };\n\n // postMessage() を受信するリスナーを登録\n window.addEventListener('message', onMessage);\n },\n\n async onOAuthCallbackReceived(authorization_status: number, authorization_detail: string) {\n console.log(`NiconicoAuthCallbackAPI: Status: ${authorization_status} / Detail: ${authorization_detail}`);\n\n // OAuth 連携に失敗した\n if (authorization_status !== 200) {\n if (authorization_detail.startsWith('Authorization was denied (access_denied)')) {\n this.$message.error('ニコニコアカウントとの連携がキャンセルされました。');\n } else if (authorization_detail.startsWith('Failed to get access token (HTTP Error ')) {\n const error = authorization_detail.replace('Failed to get access token ', '');\n this.$message.error(`アクセストークンの取得に失敗しました。${error}`);\n } else if (authorization_detail.startsWith('Failed to get access token (Connection Timeout)')) {\n this.$message.error('アクセストークンの取得に失敗しました。ニコニコで障害が発生している可能性があります。');\n } else if (authorization_detail.startsWith('Failed to get user information (HTTP Error ')) {\n const error = authorization_detail.replace('Failed to get user information ', '');\n this.$message.error(`ニコニコアカウントのユーザー情報の取得に失敗しました。${error}`);\n } else if (authorization_detail.startsWith('Failed to get user information (Connection Timeout)')) {\n this.$message.error('ニコニコアカウントのユーザー情報の取得に失敗しました。ニコニコで障害が発生している可能性があります。');\n } else {\n this.$message.error(`ニコニコアカウントとの連携に失敗しました。(${authorization_detail})`);\n }\n return;\n }\n\n // アカウント情報を強制的に更新\n await this.userStore.fetchUser(true);\n\n this.$message.success('ニコニコアカウントと連携しました。');\n },\n\n async logoutNiconicoAccount() {\n\n // ニコニコアカウント連携解除 API にリクエスト\n const result = await Niconico.logoutAccount();\n if (result === false) {\n return;\n }\n\n // アカウント情報を強制的に更新\n await this.userStore.fetchUser(true);\n\n this.$message.success('ニコニコアカウントとの連携を解除しました。');\n },\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Jikkyo.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Jikkyo.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Jikkyo.vue?vue&type=template&id=ac48731c&scoped=true&\"\nimport script from \"./Jikkyo.vue?vue&type=script&lang=ts&\"\nexport * from \"./Jikkyo.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Jikkyo.vue?vue&type=style&index=0&id=ac48731c&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"ac48731c\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('SettingsBase',[_c('h2',{staticClass:\"settings__heading\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"settings__back-button\",attrs:{\"to\":\"/settings/\"}},[_c('Icon',{attrs:{\"icon\":\"fluent:arrow-left-12-filled\",\"width\":\"25px\"}})],1),_c('Icon',{attrs:{\"icon\":\"fluent:server-surface-16-filled\",\"width\":\"22px\"}}),_c('span',{staticClass:\"ml-2\"},[_vm._v(\"サーバー設定\")])],1),_c('div',{staticClass:\"settings__content\"},[_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"鋭意開発中…\")])])])])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport Vue from 'vue';\n\nimport SettingsBase from '@/views/Settings/Base.vue';\n\nexport default Vue.extend({\n name: 'Settings-Server',\n components: {\n SettingsBase,\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Server.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Server.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Server.vue?vue&type=template&id=7cf86dbd&\"\nimport script from \"./Server.vue?vue&type=script&lang=ts&\"\nexport * from \"./Server.vue?vue&type=script&lang=ts&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('SettingsBase',[_c('h2',{staticClass:\"settings__heading\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"settings__back-button\",attrs:{\"to\":\"/settings/\"}},[_c('Icon',{attrs:{\"icon\":\"fluent:arrow-left-12-filled\",\"width\":\"25px\"}})],1),_c('Icon',{attrs:{\"icon\":\"fa-brands:twitter\",\"width\":\"22px\"}}),_c('span',{staticClass:\"ml-3\"},[_vm._v(\"Twitter\")])],1),_c('div',{staticClass:\"settings__content\",class:{'settings__content--loading': _vm.is_loading}},[_c('div',{staticClass:\"twitter-accounts\"},[(_vm.userStore.user !== null && _vm.userStore.user.twitter_accounts.length > 0)?_c('div',{staticClass:\"twitter-accounts__heading\"},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:person-board-20-filled\",\"height\":\"30\"}}),_vm._v(\"連携中のアカウント \")],1):_vm._e(),(_vm.userStore.user === null || _vm.userStore.user.twitter_accounts.length === 0)?_c('div',{staticClass:\"twitter-accounts__guide\"},[_c('Icon',{staticClass:\"flex-shrink-0\",attrs:{\"icon\":\"fa-brands:twitter\",\"width\":\"45px\"}}),_c('div',{staticClass:\"ml-4\"},[_c('div',{staticClass:\"font-weight-bold text-h6\"},[_vm._v(\"Twitter アカウントと連携していません\")]),_c('div',{staticClass:\"text--text text--darken-1 text-subtitle-2 mt-1\"},[_vm._v(\" Twitter アカウントと連携すると、テレビを見ながら Twitter にツイートしたり、ほかの実況ツイートをリアルタイムで表示できるようになります。 \")])])],1):_vm._e(),_vm._l(((_vm.userStore.user !== null ? _vm.userStore.user.twitter_accounts: [])),function(twitter_account){return _c('div',{key:twitter_account.id,staticClass:\"twitter-account\"},[_c('img',{staticClass:\"twitter-account__icon\",attrs:{\"src\":twitter_account.icon_url}}),_c('div',{staticClass:\"twitter-account__info\"},[_c('div',{staticClass:\"twitter-account__info-name\"},[_c('span',{staticClass:\"twitter-account__info-name-text\"},[_vm._v(_vm._s(twitter_account.name))])]),_c('span',{staticClass:\"twitter-account__info-screen-name\"},[_vm._v(\" @\"+_vm._s(twitter_account.screen_name)+\" \"),(twitter_account.is_oauth_session === true)?_c('span',[_vm._v(\"(Legacy Session)\")]):_vm._e()])]),_c('v-btn',{staticClass:\"twitter-account__logout ml-auto\",attrs:{\"width\":\"124\",\"height\":\"52\",\"depressed\":\"\"},on:{\"click\":function($event){return _vm.logoutTwitterAccount(twitter_account.screen_name)}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:plug-disconnected-20-filled\",\"height\":\"24\"}}),_vm._v(\"連携解除 \")],1)],1)}),_c('v-btn',{staticClass:\"twitter-account__login\",attrs:{\"color\":\"secondary\",\"max-width\":\"250\",\"height\":\"50\",\"depressed\":\"\"},on:{\"click\":function($event){return _vm.loginTwitterAccountWithPasswordForm()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:plug-connected-20-filled\",\"height\":\"24\"}}),_vm._v(\"連携するアカウントを追加 \")],1),_c('v-dialog',{attrs:{\"max-width\":\"600\"},model:{value:(_vm.twitter_password_auth_dialog),callback:function ($$v) {_vm.twitter_password_auth_dialog=$$v},expression:\"twitter_password_auth_dialog\"}},[_c('v-card',[_c('v-card-title',{staticClass:\"justify-center pt-6 font-weight-bold\"},[_vm._v(\"Twitter にログイン\")]),_c('v-card-text',{staticClass:\"pt-2 pb-0\"},[_c('p',{staticClass:\"mb-1\"},[_vm._v(\"2023/4/30 以降、Twitter のサードパーティー API の事実上の廃止により、従来のアプリ連携では Twitter にアクセスできなくなりました。\")]),_c('p',{staticClass:\"mb-1\"},[_vm._v(\"そこで KonomiTV では、代わりにユーザー名とパスワードでログインすることで、これまで通り Twitter 連携ができるようにしています (2要素認証を設定しているアカウントには対応していません) 。\")]),_c('p',{staticClass:\"mb-1\"},[_vm._v(\"万全は期していますが、非公式な方法のため、使い方次第ではアカウントにペナルティが適用される可能性もあります。自己の責任のもとでご利用ください。\")]),_c('v-form',{ref:\"twitter_form\",staticClass:\"settings__item\",on:{\"submit\":function($event){$event.preventDefault();}}},[_c('v-text-field',{ref:\"twitter_screen_name\",staticClass:\"settings__item-form mt-6\",attrs:{\"outlined\":\"\",\"label\":\"ユーザー名 (@ から始まる ID)\",\"placeholder\":\"screen_name\",\"dense\":_vm.is_form_dense,\"rules\":[(value) => !!value || 'ユーザー名を入力してください。']},model:{value:(_vm.twitter_screen_name),callback:function ($$v) {_vm.twitter_screen_name=$$v},expression:\"twitter_screen_name\"}}),_c('v-text-field',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"label\":\"パスワード\",\"dense\":_vm.is_form_dense,\"type\":_vm.twitter_password_showing ? 'text' : 'password',\"append-icon\":_vm.twitter_password_showing ? 'mdi-eye' : 'mdi-eye-off',\"rules\":[(value) => !!value || 'パスワードを入力してください。']},on:{\"click:append\":function($event){_vm.twitter_password_showing = !_vm.twitter_password_showing}},model:{value:(_vm.twitter_password),callback:function ($$v) {_vm.twitter_password=$$v},expression:\"twitter_password\"}})],1)],1),_c('v-card-actions',{staticClass:\"pt-0 px-6 pb-5\"},[_c('v-spacer'),_c('v-btn',{attrs:{\"color\":\"text\",\"height\":\"40\",\"text\":\"\"},on:{\"click\":function($event){_vm.twitter_password_auth_dialog = false}}},[_vm._v(\"キャンセル\")]),_c('v-btn',{staticClass:\"px-4\",attrs:{\"color\":\"secondary\",\"height\":\"40\"},on:{\"click\":function($event){return _vm.loginTwitterAccountWithPassword()}}},[_vm._v(\"ログイン\")])],1)],1)],1),_c('v-btn',{staticClass:\"twitter-account__login\",attrs:{\"color\":\"secondary\",\"max-width\":\"310\",\"height\":\"50\",\"depressed\":\"\"},on:{\"click\":function($event){return _vm.loginTwitterAccountWithOAuth()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:plug-connected-20-filled\",\"height\":\"24\"}}),_vm._v(\"連携するアカウントを追加 (Legacy) \")],1)],2),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"fold_panel_after_sending_tweet\"}},[_vm._v(\"ツイート送信後にパネルを折りたたむ\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"fold_panel_after_sending_tweet\"}},[_vm._v(\" この設定をオンにすると、ツイートを送信した後に、パネルが自動で折りたたまれます。\"),_c('br'),_vm._v(\" ツイートするとき以外はできるだけ映像を大きくして見たい方におすすめです。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"fold_panel_after_sending_tweet\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.fold_panel_after_sending_tweet),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"fold_panel_after_sending_tweet\", $$v)},expression:\"settingsStore.settings.fold_panel_after_sending_tweet\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"reset_hashtag_when_program_switches\"}},[_vm._v(\"番組が切り替わったときにハッシュタグフォームをリセットする\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"reset_hashtag_when_program_switches\"}},[_vm._v(\" チャンネルを切り替えたときや、視聴中の番組が終了し次の番組の放送が開始されたときに、ハッシュタグフォームをリセットするかを設定します。\"),_c('br'),_vm._v(\" この設定をオンにしておけば、「誤って前番組のハッシュタグをつけたまま次番組の実況ツイートをしてしまう」といったミスを回避できます。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"reset_hashtag_when_program_switches\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.reset_hashtag_when_program_switches),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"reset_hashtag_when_program_switches\", $$v)},expression:\"settingsStore.settings.reset_hashtag_when_program_switches\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"auto_add_watching_channel_hashtag\"}},[_vm._v(\"視聴中のチャンネルに対応する局タグを自動で追加する\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"auto_add_watching_channel_hashtag\"}},[_vm._v(\" この設定をオンにすると、視聴中のチャンネルに対応する局タグ (#nhk, #tokyomx など) がハッシュタグフォームに自動で追加されます。\"),_c('br'),_vm._v(\" 現時点で、局タグは三大首都圏の地上波・BS の一部チャンネル・AT-X にのみ対応しています。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"auto_add_watching_channel_hashtag\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.auto_add_watching_channel_hashtag),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"auto_add_watching_channel_hashtag\", $$v)},expression:\"settingsStore.settings.auto_add_watching_channel_hashtag\"}})],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"デフォルトで表示される Twitter タブ内のタブ\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" 視聴画面を開いたときに、パネルの Twitter タブの中で最初に表示されるタブを設定します。\"),_c('br')]),_c('v-select',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"items\":_vm.twitter_active_tab},model:{value:(_vm.settingsStore.settings.twitter_active_tab),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"twitter_active_tab\", $$v)},expression:\"settingsStore.settings.twitter_active_tab\"}})],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"ツイートにつけるハッシュタグの位置\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" ツイート本文から見て、ハッシュタグをどの位置につけてツイートするかを設定します。\"),_c('br')]),_c('v-select',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"items\":_vm.tweet_hashtag_position},model:{value:(_vm.settingsStore.settings.tweet_hashtag_position),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"tweet_hashtag_position\", $$v)},expression:\"settingsStore.settings.tweet_hashtag_position\"}})],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"ツイートするキャプチャに番組タイトルの透かしを描画する\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" ツイートするキャプチャに、透かしとして視聴中の番組タイトルを描画するかを設定します。\"),_c('br'),_vm._v(\" 透かしの描画位置は 左上・右上・左下・右下 から選択できます。\"),_c('br')]),_c('v-select',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"items\":_vm.tweet_capture_watermark_position},model:{value:(_vm.settingsStore.settings.tweet_capture_watermark_position),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"tweet_capture_watermark_position\", $$v)},expression:\"settingsStore.settings.tweet_capture_watermark_position\"}})],1)]),_c('v-overlay',{attrs:{\"value\":_vm.is_twitter_password_auth_sending,\"z-index\":\"300\"}},[_c('v-progress-circular',{attrs:{\"color\":\"secondary\",\"indeterminate\":\"\",\"size\":\"64\"}})],1)],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\nimport Message from '@/message';\nimport APIClient from '@/services/APIClient';\n\n\n/** Twitter アカウントと連携するための認証 URL を表すインターフェイス */\nexport interface ITwitterAuthURL {\n authorization_url: string;\n}\n\n/** ツイートの送信結果を表すインターフェイス */\nexport interface ITweetResult {\n is_success: boolean;\n tweet_url?: string;\n detail: string;\n}\n\nexport interface ITwitterPasswordAuthRequest {\n screen_name: string;\n password: string;\n}\n\n\nclass Twitter {\n\n /**\n * Twitter アカウントと連携するための認証 URL を取得する\n * @returns 認証 URL or 認証 URL の取得に失敗した場合は null\n */\n static async fetchAuthorizationURL(): Promise {\n\n // API リクエストを実行\n const response = await APIClient.get('/twitter/auth');\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'Twitter アカウントとの連携用の認証 URL を取得できませんでした。');\n return null;\n }\n\n return response.data.authorization_url;\n }\n\n\n /**\n * Twitter アカウントとパスワード認証で連携する\n * @param twitter_password_auth_request スクリーンネームとパスワード\n * @returns ログインできた場合は true, 失敗した場合は false\n */\n static async authWithPassword(twitter_password_auth_request: ITwitterPasswordAuthRequest): Promise {\n\n // API リクエストを実行\n const response = await APIClient.post('/twitter/password-auth', twitter_password_auth_request);\n\n // エラー処理\n if ('is_error' in response) {\n if (response.error.message.startsWith('Failed to authenticate with password')) {\n const error = response.error.message.match(/Message: (.+)\\)/)[1];\n Message.error(`ログインに失敗しました。${error}`);\n } else if (response.error.message.startsWith('Unexpected error occurred while authenticate with password')) {\n const error = response.error.message.match(/Message: (.+)\\)/)[1];\n Message.error(`ログインフローの途中で予期せぬエラーが発生しました。${error}`);\n } else if (response.error.message.startsWith('Failed to get user information')) {\n Message.error('Twitter アカウントのユーザー情報の取得に失敗しました。');\n } else {\n APIClient.showGenericError(response, 'Twitter アカウントとの連携に失敗しました。');\n }\n return false;\n }\n\n return true;\n }\n\n\n /**\n * 現在ログイン中のユーザーアカウントに紐づく Twitter アカウントとの連携を解除する\n * @param screen_name Twitter のスクリーンネーム\n * @returns 連携解除に成功した場合は true, 失敗した場合は false\n */\n static async logoutAccount(screen_name: string): Promise {\n\n // API リクエストを実行\n const response = await APIClient.delete(`/twitter/accounts/${screen_name}`);\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'Twitter アカウントとの連携を解除できませんでした。');\n return false;\n }\n\n return true;\n }\n\n\n /**\n * ツイートを送信する\n * @param screen_name Twitter のスクリーンネーム\n * @param text ツイート本文\n * @param captures 添付するキャプチャ画像\n */\n static async sendTweet(screen_name: string, text: string, captures: Blob[]): Promise<{message: string; is_error: boolean;}> {\n\n // multipart/form-data でツイート本文と画像(選択されている場合)を送る\n const form_data = new FormData();\n form_data.append('tweet', text);\n for (const tweet_capture of captures) {\n form_data.append('images', tweet_capture);\n }\n\n // API リクエストを実行\n const response = await APIClient.post(`/twitter/accounts/${screen_name}/tweets`, form_data, {\n headers: {'Content-Type': 'multipart/form-data'},\n });\n\n // エラー処理 (API リクエスト自体に失敗した場合)\n if ('is_error' in response) {\n if (response.error.message) {\n if (Number.isNaN(response.status)) {\n return {message: `エラー: ツイートの送信に失敗しました。(${response.error.message})`, is_error: true};\n } else {\n return {message: `エラー: ツイートの送信に失敗しました。(HTTP Error ${response.status} / ${response.error.message})`, is_error: true};\n }\n } else {\n return {message: `エラー: ツイートの送信に失敗しました。(HTTP Error ${response.status})`, is_error: true};\n }\n }\n\n // 成功 or 失敗に関わらず detail の内容をそのまま通知する\n if (response.data.is_success === true) {\n // ツイート成功\n return {message: response.data.detail, is_error: false};\n } else {\n // ツイート失敗\n return {message: `エラー: ${response.data.detail}`, is_error: true};\n }\n }\n}\n\nexport default Twitter;\n","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport Twitter from '@/services/Twitter';\nimport useSettingsStore from '@/store/SettingsStore';\nimport useUserStore from '@/store/UserStore';\nimport Utils from '@/utils';\nimport SettingsBase from '@/views/Settings/Base.vue';\n\nexport default Vue.extend({\n name: 'Settings-Twitter',\n components: {\n SettingsBase,\n },\n data() {\n return {\n\n // フォームを小さくするかどうか\n is_form_dense: Utils.isSmartphoneHorizontal(),\n\n // デフォルトで表示されるパネルのタブの選択肢\n twitter_active_tab: [\n {text: 'ツイート検索タブ', value: 'Search'},\n {text: 'タイムラインタブ', value: 'Timeline'},\n {text: 'キャプチャタブ', value: 'Capture'},\n ],\n\n // ツイートにつけるハッシュタグの位置の選択肢\n tweet_hashtag_position: [\n {text: 'ツイート本文の前に追加する', value: 'Prepend'},\n {text: 'ツイート本文の後に追加する', value: 'Append'},\n {text: 'ツイート本文の前に追加してから改行する', value: 'PrependWithLineBreak'},\n {text: 'ツイート本文の後に改行してから追加する', value: 'AppendWithLineBreak'},\n ],\n\n // ツイートするキャプチャに番組タイトルの透かしを描画する位置の選択肢\n tweet_capture_watermark_position: [\n {text: '透かしを描画しない', value: 'None'},\n {text: '透かしをキャプチャの左上に描画する', value: 'TopLeft'},\n {text: '透かしをキャプチャの右上に描画する', value: 'TopRight'},\n {text: '透かしをキャプチャの左下に描画する', value: 'BottomLeft'},\n {text: '透かしをキャプチャの右下に描画する', value: 'BottomRight'},\n ],\n\n // ローディング中かどうか\n is_loading: true,\n\n // パスワード認証実行中かどうか\n is_twitter_password_auth_sending: false,\n\n // パスワード認証用ダイヤログ\n twitter_password_auth_dialog: false,\n\n // Twitter のスクリーンネームとパスワード\n twitter_screen_name: '',\n twitter_password: '',\n twitter_password_showing: false,\n };\n },\n computed: {\n // SettingsStore / UserStore に this.settingsStore / this.userStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useSettingsStore, useUserStore),\n },\n async created() {\n\n // アカウント情報を更新\n await this.userStore.fetchUser();\n\n // ローディング状態を解除\n this.is_loading = false;\n\n // もしハッシュ (# から始まるフラグメント) に何か指定されていたら、\n // OAuth 連携のコールバックの結果が入っている可能性が高いので、パースを試みる\n // アカウント情報更新より後にしないと Snackbar がうまく表示されない\n if (location.hash !== '') {\n const params = new URLSearchParams(location.hash.replace('#', ''));\n if (params.get('status') !== null && params.get('detail') !== null) {\n // コールバックの結果を取得できたので、OAuth 連携の結果を画面に通知する\n const authorization_status = parseInt(params.get('status')!);\n const authorization_detail = params.get('detail')!;\n this.onOAuthCallbackReceived(authorization_status, authorization_detail);\n // URL からフラグメントを削除\n // ref: https://stackoverflow.com/a/49373716/17124142\n history.replaceState(null, '', ' ');\n }\n }\n },\n methods: {\n async loginTwitterAccountWithPasswordForm() {\n // ログインしていない場合はエラーにする\n if (this.userStore.is_logged_in === false) {\n this.$message.warning('連携をはじめるには、KonomiTV アカウントにログインしてください。');\n await Utils.sleep(0.01);\n this.twitter_password_auth_dialog = false;\n return;\n }\n this.twitter_password_auth_dialog = true;\n },\n\n async loginTwitterAccountWithPassword() {\n\n // バリデーションを実行\n if ((this.$refs.twitter_form as any).validate() === false) {\n return;\n }\n\n // Twitter パスワード認証 API にリクエスト\n this.is_twitter_password_auth_sending = true;\n const result = await Twitter.authWithPassword({\n screen_name: this.twitter_screen_name,\n password: this.twitter_password,\n });\n this.is_twitter_password_auth_sending = false;\n if (result === false) {\n return;\n }\n\n // アカウント情報を強制的に更新\n await this.userStore.fetchUser(true);\n if (this.userStore.user === null) {\n this.$message.error('アカウント情報を取得できませんでした。');\n return;\n }\n\n // ログイン中のユーザーに紐づく Twitter アカウントのうち、一番 updated_at が新しいものを取得\n // ログインすると updated_at が更新されるため、この時点で一番 updated_at が新しいアカウントが今回連携したものだと判断できる\n // ref: https://stackoverflow.com/a/12192544/17124142 (ISO8601 のソートアルゴリズム)\n const current_twitter_account = [...this.userStore.user.twitter_accounts].sort((a, b) => {\n return (a.updated_at < b.updated_at) ? 1 : ((a.updated_at > b.updated_at) ? -1 : 0);\n })[0];\n\n this.$message.success(`Twitter @${current_twitter_account.screen_name} と連携しました。`);\n\n // フォームをリセットし、非表示にする\n (this.$refs.twitter_form as any).reset();\n this.twitter_password_auth_dialog = false;\n },\n\n async loginTwitterAccountWithOAuth() {\n\n // ログインしていない場合はエラーにする\n if (this.userStore.is_logged_in === false) {\n this.$message.warning('連携をはじめるには、KonomiTV アカウントにログインしてください。');\n return;\n }\n\n // Twitter アカウントと連携するための認証 URL を取得\n const authorization_url = await Twitter.fetchAuthorizationURL();\n if (authorization_url === null) {\n return;\n }\n\n // モバイルデバイスではポップアップが事実上使えない (特に Safari ではブロックされてしまう) ので、素直にリダイレクトで実装する\n if (Utils.isMobileDevice() === true) {\n location.href = authorization_url;\n return;\n }\n\n // OAuth 連携のため、認証 URL をポップアップウインドウで開く\n // window.open() の第2引数はユニークなものにしておくと良いらしい\n // ref: https://qiita.com/catatsuy/items/babce8726ea78f5d25b1 (大変参考になりました)\n const popup_window = window.open(authorization_url, 'KonomiTV-OAuthPopup', Utils.getWindowFeatures());\n if (popup_window === null) {\n this.$message.error('ポップアップウインドウを開けませんでした。');\n return;\n }\n\n // 認証完了 or 失敗後、ポップアップウインドウから送信される文字列を受信\n const onMessage = async (event) => {\n\n // すでにウインドウが閉じている場合は実行しない\n if (popup_window.closed) return;\n\n // 受け取ったオブジェクトに KonomiTV-OAuthPopup キーがない or そもそもオブジェクトではない際は実行しない\n // ブラウザの拡張機能から結構余計な message が飛んでくるっぽい…。\n if (Utils.typeof(event.data) !== 'object') return;\n if (('KonomiTV-OAuthPopup' in event.data) === false) return;\n\n // 認証は完了したので、ポップアップウインドウを閉じ、リスナーを解除する\n if (popup_window) popup_window.close();\n window.removeEventListener('message', onMessage);\n\n // ステータスコードと詳細メッセージを取得\n const authorization_status = event.data['KonomiTV-OAuthPopup']['status'] as number;\n const authorization_detail = event.data['KonomiTV-OAuthPopup']['detail'] as string;\n this.onOAuthCallbackReceived(authorization_status, authorization_detail);\n };\n\n // postMessage() を受信するリスナーを登録\n window.addEventListener('message', onMessage);\n },\n\n async onOAuthCallbackReceived(authorization_status: number, authorization_detail: string) {\n console.log(`TwitterAuthCallbackAPI: Status: ${authorization_status} / Detail: ${authorization_detail}`);\n\n // OAuth 連携に失敗した\n if (authorization_status !== 200) {\n if (authorization_detail.startsWith('Authorization was denied by user')) {\n this.$message.error('Twitter アカウントとの連携がキャンセルされました。');\n } else if (authorization_detail.startsWith('Failed to get access token')) {\n this.$message.error('アクセストークンの取得に失敗しました。');\n } else if (authorization_detail.startsWith('Failed to get user information')) {\n this.$message.error('Twitter アカウントのユーザー情報の取得に失敗しました。');\n } else {\n this.$message.error(`Twitter アカウントとの連携に失敗しました。(${authorization_detail})`);\n }\n return;\n }\n\n // アカウント情報を強制的に更新\n await this.userStore.fetchUser(true);\n if (this.userStore.user === null) {\n this.$message.error('アカウント情報を取得できませんでした。');\n return;\n }\n\n // ログイン中のユーザーに紐づく Twitter アカウントのうち、一番 updated_at が新しいものを取得\n // ログインすると updated_at が更新されるため、この時点で一番 updated_at が新しいアカウントが今回連携したものだと判断できる\n // ref: https://stackoverflow.com/a/12192544/17124142 (ISO8601 のソートアルゴリズム)\n const current_twitter_account = [...this.userStore.user.twitter_accounts].sort((a, b) => {\n return (a.updated_at < b.updated_at) ? 1 : ((a.updated_at > b.updated_at) ? -1 : 0);\n })[0];\n\n this.$message.success(`Twitter @${current_twitter_account.screen_name} と連携しました。`);\n },\n\n async logoutTwitterAccount(screen_name: string) {\n\n // Twitter アカウント連携解除 API にリクエスト\n const result = await Twitter.logoutAccount(screen_name);\n if (result === false) {\n return;\n }\n\n // アカウント情報を強制的に更新\n await this.userStore.fetchUser(true);\n\n this.$message.success(`Twitter @${screen_name} との連携を解除しました。`);\n },\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Twitter.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Twitter.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Twitter.vue?vue&type=template&id=ea90430c&scoped=true&\"\nimport script from \"./Twitter.vue?vue&type=script&lang=ts&\"\nexport * from \"./Twitter.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Twitter.vue?vue&type=style&index=0&id=ea90430c&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"ea90430c\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"route-container\"},[_c('Header'),_c('main',[_c('Navigation'),_c('div',{staticClass:\"channels-container channels-container--home\",class:{'channels-container--loading': _vm.is_loading}},[_c('v-tabs-fix',{staticClass:\"channels-tab\",attrs:{\"centered\":\"\"},model:{value:(_vm.tab),callback:function ($$v) {_vm.tab=$$v},expression:\"tab\"}},_vm._l((Array.from(_vm.channelsStore.channels_list_with_pinned)),function([channels_type,]){return _c('v-tab',{key:channels_type,staticClass:\"channels-tab__item\"},[_vm._v(\" \"+_vm._s(channels_type)+\" \")])}),1),_c('v-tabs-items-fix',{staticClass:\"channels-list\",model:{value:(_vm.tab),callback:function ($$v) {_vm.tab=$$v},expression:\"tab\"}},_vm._l((Array.from(_vm.channelsStore.channels_list_with_pinned)),function([channels_type, channels]){return _c('v-tab-item-fix',{key:channels_type,staticClass:\"channels-tabitem\"},[_c('div',{staticClass:\"channels\",class:`channels--tab-${channels_type} channels--length-${channels.length}`},[_vm._l((channels),function(channel){return _c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],key:channel.id,staticClass:\"channel\",attrs:{\"to\":`/tv/watch/${channel.channel_id}`}},[_c('div',{staticClass:\"channel__broadcaster\"},[_c('img',{staticClass:\"channel__broadcaster-icon\",attrs:{\"src\":`${_vm.Utils.api_base_url}/channels/${channel.channel_id}/logo`}}),_c('div',{staticClass:\"channel__broadcaster-content\"},[_c('span',{staticClass:\"channel__broadcaster-name\"},[_vm._v(\"Ch: \"+_vm._s(channel.channel_number)+\" \"+_vm._s(channel.channel_name))]),_c('div',{staticClass:\"channel__broadcaster-status\"},[_c('div',{staticClass:\"channel__broadcaster-status-force\",class:`channel__broadcaster-status-force--${_vm.ChannelUtils.getChannelForceType(channel.channel_force)}`},[_c('Icon',{attrs:{\"icon\":\"fa-solid:fire-alt\",\"height\":\"12px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(\"勢い:\")]),_c('span',{staticClass:\"ml-1\"},[_vm._v(_vm._s(_vm.ProgramUtils.getAttribute(channel, 'channel_force', '--')))]),_c('span',{staticStyle:{\"margin-left\":\"3px\"}},[_vm._v(\" コメ/分\")])],1),_c('div',{staticClass:\"channel__broadcaster-status-viewers ml-4\"},[_c('Icon',{attrs:{\"icon\":\"fa-solid:eye\",\"height\":\"14px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(\"視聴数:\")]),_c('span',{staticClass:\"ml-1\"},[_vm._v(_vm._s(channel.viewers))])],1)])]),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip\",value:(_vm.isPinnedChannel(channel.channel_id) ? 'ピン留めを外す' : 'ピン留めする'),expression:\"isPinnedChannel(channel.channel_id) ? 'ピン留めを外す' : 'ピン留めする'\"}],staticClass:\"channel__broadcaster-pin\",class:{'channel__broadcaster-pin--pinned': _vm.isPinnedChannel(channel.channel_id)},on:{\"click\":function($event){$event.preventDefault();$event.stopPropagation();_vm.isPinnedChannel(channel.channel_id) ? _vm.removePinnedChannel(channel.channel_id) : _vm.addPinnedChannel(channel.channel_id)},\"mousedown\":function($event){$event.preventDefault();$event.stopPropagation();/* 親要素の波紋が広がらないように */}}},[_c('Icon',{attrs:{\"icon\":\"fluent:pin-20-filled\",\"width\":\"24px\"}})],1)]),_c('div',{staticClass:\"channel__program-present\"},[_c('div',{staticClass:\"channel__program-present-title-wrapper\"},[_c('span',{staticClass:\"channel__program-present-title\",domProps:{\"innerHTML\":_vm._s(_vm.ProgramUtils.decorateProgramInfo(channel.program_present, 'title'))}}),_c('span',{staticClass:\"channel__program-present-time\"},[_vm._v(_vm._s(_vm.ProgramUtils.getProgramTime(channel.program_present)))])]),_c('span',{staticClass:\"channel__program-present-description\",domProps:{\"innerHTML\":_vm._s(_vm.ProgramUtils.decorateProgramInfo(channel.program_present, 'description'))}})]),_c('v-spacer'),_c('div',{staticClass:\"channel__program-following\"},[_c('div',{staticClass:\"channel__program-following-title\"},[_c('span',{staticClass:\"channel__program-following-title-decorate\"},[_vm._v(\"NEXT\")]),_c('Icon',{staticClass:\"channel__program-following-title-icon\",attrs:{\"icon\":\"fluent:fast-forward-20-filled\",\"width\":\"16px\"}}),_c('span',{staticClass:\"channel__program-following-title-text\",domProps:{\"innerHTML\":_vm._s(_vm.ProgramUtils.decorateProgramInfo(channel.program_following, 'title'))}})],1),_c('span',{staticClass:\"channel__program-following-time\"},[_vm._v(_vm._s(_vm.ProgramUtils.getProgramTime(channel.program_following)))])]),_c('div',{staticClass:\"channel__progressbar\"},[_c('div',{staticClass:\"channel__progressbar-progress\",style:(`width:${_vm.ProgramUtils.getProgramProgress(channel.program_present)}%;`)})])],1)}),(channels_type === 'ピン留め' && channels.length === 0)?_c('div',{staticClass:\"pinned-container d-flex justify-center align-center w-100\"},[_c('div',{staticClass:\"d-flex justify-center align-center flex-column\"},[_c('h2',[_vm._v(\"ピン留めされているチャンネルが\"),_c('br'),_vm._v(\"ありません。\")]),_c('div',{staticClass:\"mt-4 text--text text--darken-1\"},[_vm._v(\"各チャンネルの \"),_c('Icon',{staticStyle:{\"position\":\"relative\",\"bottom\":\"-5px\"},attrs:{\"icon\":\"fluent:pin-20-filled\",\"width\":\"22px\"}}),_vm._v(\" アイコンから、よくみる\"),_c('br'),_vm._v(\"チャンネルをこのタブにピン留めできます。\")],1),_c('div',{staticClass:\"mt-2 text--text text--darken-1\"},[_vm._v(\"チャンネルをピン留めすると、\"),_c('br'),_vm._v(\"このタブが最初に表示されます。\")])])]):_vm._e()],2)])}),1)],1)],1)],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n/** 番組情報を表すインターフェイス */\nexport interface IProgram {\n id: string;\n network_id: number;\n service_id: number;\n event_id: number;\n channel_id: string;\n title: string;\n description: string;\n detail: {[key: string]: string};\n start_time: string;\n end_time: string;\n duration: number;\n is_free: boolean;\n genre: {major: string; middle: string}[];\n video_type: string;\n video_codec: string;\n video_resolution: string;\n primary_audio_type: string;\n primary_audio_language: string;\n primary_audio_sampling_rate: string;\n secondary_audio_type: string | null;\n secondary_audio_language: string | null;\n secondary_audio_sampling_rate: string | null;\n}\n\n/** 番組情報を表すインターフェイスのデフォルト値 */\nexport const IProgramDefault: IProgram = {\n id: 'NID0-SID0-EID0',\n network_id: 0,\n service_id: 0,\n event_id: 0,\n channel_id: 'gr000',\n title: '取得中…',\n description: '取得中…',\n detail: {},\n start_time: '2000-01-01T00:00:00+09:00',\n end_time: '2000-01-01T00:00:00+09:00',\n duration: 0,\n is_free: true,\n genre: [],\n video_type: '映像1080i(1125i)、アスペクト比16:9 パンベクトルなし',\n video_codec: 'mpeg2',\n video_resolution: '1080i',\n primary_audio_type: '2/0モード(ステレオ)',\n primary_audio_language: '日本語',\n primary_audio_sampling_rate: '48kHz',\n secondary_audio_type: null,\n secondary_audio_language: null,\n secondary_audio_sampling_rate: null,\n};\n\n// TODO: 番組情報 API が開発されたらここに API 定義を書く\n","\nimport APIClient from '@/services/APIClient';\nimport { IProgram, IProgramDefault } from '@/services/Programs';\n\n\n/** チャンネルタイプの型 */\nexport type ChannelType = 'GR' | 'BS' | 'CS' | 'CATV' | 'SKY' | 'STARDIGIO';\n\n// チャンネルタイプの型 (実際のチャンネルリストに表示される表現)\nexport type ChannelTypePretty = 'ピン留め' | '地デジ' | 'BS' | 'CS' | 'CATV' | 'SKY' | 'StarDigio';\n\n/** すべてのチャンネルタイプのチャンネルの情報を表すインターフェイス */\nexport interface IChannelsList {\n GR: IChannel[];\n BS: IChannel[];\n CS: IChannel[];\n CATV: IChannel[];\n SKY: IChannel[];\n STARDIGIO: IChannel[];\n}\n\n/** チャンネル情報を表すインターフェイス */\nexport interface IChannel {\n id: string;\n network_id: number;\n service_id: number;\n transport_stream_id: number | null;\n remocon_id: number | null;\n channel_id: string;\n channel_number: string;\n channel_name: string;\n channel_type: ChannelType;\n channel_force: number | null;\n channel_comment: number | null;\n is_subchannel: boolean;\n is_radiochannel: boolean;\n is_display: boolean;\n viewers: number;\n program_present: IProgram | null;\n program_following: IProgram | null;\n}\n\n/** チャンネル情報を表すインターフェイスのデフォルト値 */\nexport const IChannelDefault: IChannel = {\n id: 'NID0-SID0',\n network_id: 0,\n service_id: 0,\n transport_stream_id: null,\n remocon_id: null,\n channel_id: 'gr000',\n channel_number: '---',\n channel_name: '取得中…',\n channel_type: 'GR',\n channel_force: null,\n channel_comment: null,\n is_subchannel: false,\n is_radiochannel: false,\n is_display: true,\n viewers: 0,\n program_present: IProgramDefault,\n program_following: IProgramDefault,\n};\n\n/** ニコニコ実況のセッション情報を表すインターフェイス */\nexport interface IJikkyoSession {\n is_success: boolean;\n audience_token: string | null;\n detail: string;\n}\n\n\nclass Channels {\n\n /**\n * すべてのチャンネルの情報を取得する\n * @return すべてのチャンネルの情報\n */\n static async fetchAll(): Promise {\n\n // API リクエストを実行\n const response = await APIClient.get('/channels');\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'チャンネル情報を取得できませんでした。');\n return null;\n }\n\n return response.data;\n }\n\n\n /**\n * 指定したチャンネルの情報を取得する\n * 現状、処理の見直しにより使用されていない\n * @param channel_id チャンネル ID\n * @return 指定したチャンネルの情報\n */\n static async fetch(channel_id: string): Promise {\n\n // API リクエストを実行\n const response = await APIClient.get(`/channels/${channel_id}`);\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'チャンネル情報を取得できませんでした。');\n return null;\n }\n\n return response.data;\n }\n\n\n /**\n * 指定したチャンネルに紐づくニコニコ実況のセッション情報を取得する\n * @param channel_id チャンネル ID\n * @return 指定したチャンネルに紐づくニコニコ実況のセッション情報\n */\n static async fetchJikkyoSession(channel_id: string): Promise {\n\n // API リクエストを実行\n const response = await APIClient.get(`/channels/${channel_id}/jikkyo`);\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'ニコニコ実況のセッション情報を取得できませんでした。');\n return null;\n }\n\n return response.data;\n }\n}\n\nexport default Channels;\n","\nimport { defineStore } from 'pinia';\nimport Vue from 'vue';\n\nimport Channels, { ChannelType, ChannelTypePretty, IChannelsList, IChannel, IChannelDefault } from '@/services/Channels';\nimport useSettingsStore from '@/store/SettingsStore';\nimport Utils, { ChannelUtils } from '@/utils';\n\n\n/**\n * TV ホーム画面と TV 視聴画面の両方のページでチャンネル情報を共有するためのストア\n * チャンネル情報の API からの取得はかなり重めなので、ページ遷移時に毎回 API リクエストを行うのはパフォーマンスが悪い\n * チャンネル情報をストアに格納しておくことで、TV ホーム画面から TV 視聴画面に遷移したときのパフォーマンスが向上する\n */\nconst useChannelsStore = defineStore('channels', {\n state: () => ({\n\n // 現在視聴中のチャンネルの ID (ex: gr011)\n // 視聴画面のみ有効で、ホーム画面では利用されない\n channel_id: 'gr000' as string,\n\n // すべてのチャンネルタイプのチャンネルリスト\n channels_list: {\n GR: [],\n BS: [],\n CS: [],\n CATV: [],\n SKY: [],\n STARDIGIO: [],\n } as IChannelsList,\n\n // 初回のチャンネル情報更新が実行された後かどうか\n is_channels_list_initial_updated: false,\n\n // 最終更新日時 (UNIX タイムスタンプ、秒単位)\n last_updated_at: 0,\n }),\n getters: {\n\n /**\n * ライブ視聴画面を表示中かどうか\n * チャンネル情報がセットされているかどうかで判定できる\n */\n is_showing_live(): boolean {\n return this.channel_id !== 'gr000';\n },\n\n /**\n * 前・現在・次のチャンネル情報 (視聴画面用)\n * チャンネル情報はデータ量がかなり多いので、個別に取得するより一気に取得したほうがループ回数が少なくなりパフォーマンスが良い\n */\n channel(): {previous: IChannel; current: IChannel; next: IChannel;} {\n\n // チャンネルタイプごとのチャンネル情報リストを取得する (すべてのチャンネルリストから探索するより効率的)\n const channels: IChannel[] | undefined = this.channels_list[ChannelUtils.getChannelType(this.channel_id)];\n\n // まだチャンネルリストの更新が終わっていないなどの場合で取得できなかった場合、\n // null を返すと UI 側でのエラー処理が大変なので、暫定的なダミーのチャンネル情報を返す\n if (channels === undefined || channels.length === 0) {\n return {\n previous: IChannelDefault,\n current: IChannelDefault,\n next: IChannelDefault,\n };\n }\n\n // 起点にするチャンネル情報があるインデックスを取得\n const current_channel_index = channels.findIndex((channel) => channel.channel_id === this.channel_id);\n\n // インデックスが取得できなかった場合も同様に、暫定的なダミーのチャンネル情報を返す\n if (current_channel_index === -1) {\n const IProgramError = {\n ...IChannelDefault.program_present,\n channel_id: 'gr999',\n title: 'チャンネル情報取得エラー',\n description: 'このチャンネル ID のチャンネル情報は存在しません。',\n };\n const IChannelError = {\n ...IChannelDefault,\n channel_id: 'gr999', // チャンネル情報が存在しないことを示す特殊なチャンネル ID\n channel_name: 'ERROR',\n program_present: IProgramError,\n program_following: IProgramError,\n };\n return {\n previous: IChannelError,\n current: IChannelError,\n next: IChannelError,\n };\n }\n\n // 前のインデックスを取得する\n // インデックスがマイナスになった時は、最後のインデックスに巻き戻す\n // channel.is_display が true のチャンネルに到達するまで続ける\n const previous_channel_index = ((): number => {\n let index = current_channel_index - 1;\n while (channels.length) {\n if (index <= -1) {\n index = channels.length - 1; // 最後のインデックス\n }\n if (channels[index].is_display) {\n return index;\n }\n index--;\n }\n return 0;\n })();\n\n // 次のインデックスを取得する\n // インデックスが配列の長さを超えた時は、最初のインデックスに巻き戻す\n // channel.is_display が true のチャンネルに到達するまで続ける\n const next_channel_index = ((): number => {\n let index = current_channel_index + 1;\n while (channels.length) {\n if (index >= channels.length) {\n index = 0; // 最初のインデックス\n }\n if (channels[index].is_display) {\n return index;\n }\n index++;\n }\n return 0;\n })();\n\n // 前・現在・次のチャンネル情報を返す\n return {\n previous: channels[previous_channel_index],\n current: channels[current_channel_index],\n next: channels[next_channel_index],\n };\n },\n\n /**\n * 実際に表示されるチャンネルリストを表すデータ\n * ピン留めチャンネルのタブを追加するほか、放送していないサブチャンネルはピン留めタブを含めて表示から除外される\n * また、チャンネルが1つもないチャンネルタイプのタブも表示から除外される\n * (たとえば SKY (スカパー!プレミアムサービス) のタブは、SKY に属すチャンネルが1つもない(=受信できない)なら表示されない)\n */\n channels_list_with_pinned(): Map {\n\n const settings_store = useSettingsStore();\n\n // 事前に Map を定義しておく\n // Map にしていたのは、確か連想配列の順序を保証してくれるからだったはず\n const channels_list_with_pinned = new Map();\n channels_list_with_pinned.set('ピン留め', []);\n channels_list_with_pinned.set('地デジ', []);\n\n // 初回のチャンネル情報更新がまだ実行されていない or 実行中のときは最低限のこの2つだけで返す\n if (this.is_channels_list_initial_updated === false) {\n return channels_list_with_pinned;\n }\n\n channels_list_with_pinned.set('BS', []);\n channels_list_with_pinned.set('CS', []);\n channels_list_with_pinned.set('CATV', []);\n channels_list_with_pinned.set('SKY', []);\n channels_list_with_pinned.set('StarDigio', []);\n\n // channels_list に格納されているすべてのチャンネルに対しループを回し、\n // 順次 channels_list_with_pinned に追加していく\n // 1つのチャンネルに対するループ回数が少なくなる分、毎回 filter() や find() するよりも高速になるはず\n for (const [channel_type, channels] of Object.entries(this.channels_list)) {\n for (const channel of channels) {\n\n // 放送していないサブチャンネルは除外\n if (channel.is_display === false) {\n continue;\n }\n\n // ピン留めしているチャンネルの ID (ex: gr011) が入るリストに含まれていたら、ピン留めタブに追加\n if (settings_store.settings.pinned_channel_ids.includes(channel.channel_id)) {\n channels_list_with_pinned.get('ピン留め')?.push(channel);\n }\n\n // チャンネルタイプごとに分類\n switch (channel.channel_type) {\n case 'GR': {\n channels_list_with_pinned.get('地デジ')?.push(channel);\n break;\n }\n case 'BS': {\n channels_list_with_pinned.get('BS')?.push(channel);\n break;\n }\n case 'CS': {\n channels_list_with_pinned.get('CS')?.push(channel);\n break;\n }\n case 'CATV': {\n channels_list_with_pinned.get('CATV')?.push(channel);\n break;\n }\n case 'SKY': {\n channels_list_with_pinned.get('SKY')?.push(channel);\n break;\n }\n case 'STARDIGIO': {\n channels_list_with_pinned.get('StarDigio')?.push(channel);\n break;\n }\n }\n }\n }\n\n // ピン留めチャンネルを追加順に並び替える\n for (const channel of [...channels_list_with_pinned.get('ピン留め')!]) {\n const index = settings_store.settings.pinned_channel_ids.indexOf(channel.channel_id);\n channels_list_with_pinned.get('ピン留め')![index] = channel;\n }\n\n // 最後に、チャンネルが1つもないチャンネルタイプのタブを除外する (ピン留めタブを除く)\n for (const [channel_type, channels] of channels_list_with_pinned) {\n if (channel_type === 'ピン留め') {\n continue;\n }\n if (channels.length === 0) {\n channels_list_with_pinned.delete(channel_type);\n }\n }\n\n // ただし、this.channels_list_with_pinned 全体が空でもうピン留めタブしか残っていない場合は、ピン留めタブも削除する\n if (channels_list_with_pinned.size === 1 && channels_list_with_pinned.has('ピン留め')) {\n channels_list_with_pinned.delete('ピン留め');\n }\n\n return channels_list_with_pinned;\n },\n\n /**\n * 視聴画面向けの channels_list_with_pinned\n * 視聴画面ではピン留めされているチャンネルが1つもないときは、ピン留めタブを表示する必要性がないため削除される\n */\n channels_list_with_pinned_for_watch(): Map {\n const channels_list_with_pinned = new Map([...this.channels_list_with_pinned]);\n if (channels_list_with_pinned.get('ピン留め')?.length === 0) {\n channels_list_with_pinned.delete('ピン留め');\n }\n return channels_list_with_pinned;\n }\n },\n actions: {\n\n /**\n * 指定されたチャンネル ID のチャンネル情報を取得する\n * @param channel_id 取得するチャンネル ID (ex: gr011)\n * @returns チャンネル情報\n */\n getChannel(channel_id: string): IChannel | null {\n\n // チャンネルタイプごとのチャンネル情報リストを取得する (すべてのチャンネルリストから探索するより効率的)\n const channels = this.channels_list[ChannelUtils.getChannelType(channel_id)];\n if (channels === undefined) {\n return null;\n }\n\n // チャンネル ID が一致するチャンネル情報を返す\n return channels.find(channel => channel.channel_id === channel_id) ?? null;\n },\n\n /**\n * チャンネルタイプとリモコン番号からチャンネル情報を取得する\n * @param channel_type チャンネルタイプ\n * @param remocon_id リモコン番号\n * @returns チャンネル情報 (見つからなかった場合は null)\n */\n getChannelByRemoconID(channel_type: ChannelType, remocon_id: number): IChannel | null {\n\n // 指定されたチャンネルタイプのチャンネルを取得\n const channels = this.channels_list[channel_type];\n\n // リモコン番号が一致するチャンネルを取得\n const channel = channels.find((channel) => channel.remocon_id === remocon_id);\n\n // リモコン番号が一致するチャンネルを見つけられなかった場合は null を返す\n return channel ?? null;\n },\n\n /**\n * 指定されたチャンネル ID のチャンネル情報を更新する\n * 今のところ viewers (視聴者数) を更新する目的でしか使っていない\n * @param channel_id 更新するチャンネル ID (ex: gr011)\n * @param channel 更新後のチャンネル情報\n */\n updateChannel(channel_id: string, channel: IChannel): void {\n\n // チャンネルタイプごとのチャンネル情報リストを取得する (すべてのチャンネルリストから探索するより効率的)\n const channel_type = ChannelUtils.getChannelType(channel_id);\n if (this.channels_list[channel_type] === undefined) {\n return null;\n }\n\n // チャンネル ID が一致するチャンネル情報を更新する\n const index = this.channels_list[channel_type].findIndex(channel => channel.channel_id === channel_id);\n if (index === -1) {\n return;\n }\n // リアクティブにするために Vue.set を使う\n Vue.set(this.channels_list[channel_type], index, channel);\n },\n\n /**\n * チャンネルリストを更新する\n * @param force 強制的に更新するかどうか\n */\n async update(force: boolean = false): Promise {\n\n const update = async () => {\n\n // 最新のすべてのチャンネルの情報を取得\n const channels_list = await Channels.fetchAll();\n if (channels_list === null) {\n return;\n }\n\n this.channels_list = channels_list;\n this.is_channels_list_initial_updated = true;\n this.last_updated_at = Utils.time();\n };\n\n // すでに取得されている場合は更新しない\n if (this.is_channels_list_initial_updated === true && force === false) {\n\n // ただし、最終更新日時が1分以上前の場合は非同期で更新する\n if (Utils.time() - this.last_updated_at > 60) {\n update();\n }\n\n return;\n }\n\n // チャンネルリストの更新を行う\n await update();\n }\n }\n});\n\nexport default useChannelsStore;\n","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport Header from '@/components/Header.vue';\nimport Navigation from '@/components/Navigation.vue';\nimport useChannelsStore from '@/store/ChannelsStore';\nimport useSettingsStore from '@/store/SettingsStore';\nimport Utils, { ChannelUtils, ProgramUtils } from '@/utils';\n\nexport default Vue.extend({\n name: 'TV-Home',\n components: {\n Header,\n Navigation,\n },\n data() {\n return {\n\n // ユーティリティをテンプレートで使えるように\n Utils: Utils,\n ChannelUtils: ChannelUtils,\n ProgramUtils: ProgramUtils,\n\n // タブの状態管理\n tab: null as number | null,\n\n // ローディング中かどうか\n is_loading: true,\n\n // インターバル ID\n // ページ遷移時に setInterval(), setTimeout() の実行を止めるのに使う\n // setInterval(), setTimeout() の返り値を登録する\n interval_ids: [] as number[],\n };\n },\n computed: {\n // ChannelsStore / SettingsStore に this.channelsStore / this.settingsStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useChannelsStore, useSettingsStore),\n },\n // 開始時に実行\n async created() {\n\n // ピン留めされているチャンネルの ID が空なら、タブを地デジタブに切り替える\n // ピン留めができる事を示唆するためにピン留めタブ自体は残す\n if (this.settingsStore.settings.pinned_channel_ids.length === 0) {\n this.tab = 1;\n }\n\n // 00秒までの残り秒数を取得\n // 現在 16:01:34 なら 26 (秒) になる\n const residue_second = 60 - new Date().getSeconds();\n\n // 00秒になるまで待ってから実行するタイマー\n // 番組は基本1分単位で組まれているため、20秒や45秒など中途半端な秒数で更新してしまうと番組情報の反映が遅れてしまう\n this.interval_ids.push(window.setTimeout(() => {\n\n // この時点で00秒なので、チャンネル情報を更新\n this.channelsStore.update(true);\n\n // 以降、30秒おきにチャンネル情報を更新\n this.interval_ids.push(window.setInterval(() => this.channelsStore.update(true), 30 * 1000));\n\n }, residue_second * 1000));\n\n // チャンネル情報を更新 (初回)\n await this.channelsStore.update();\n\n // 少しだけ待つ\n // v-tabs-slider-wrapper をアニメーションさせないために必要\n await Utils.sleep(0.01);\n\n // この時点でピン留めされているチャンネルがないなら、タブを地デジタブに切り替える\n // ピン留めされているチャンネル自体はあるが、現在放送されていないため表示できない場合に備える\n if (this.channelsStore.channels_list_with_pinned.get('ピン留め').length === 0) {\n this.tab = 1;\n }\n\n // チャンネル情報の更新が終わったタイミングでローディング状態を解除する\n this.is_loading = false;\n },\n // 終了前に実行\n beforeDestroy() {\n\n // clearInterval() ですべての setInterval(), setTimeout() の実行を止める\n // clearInterval() と clearTimeout() は中身共通なので問題ない\n for (const interval_id of this.interval_ids) {\n window.clearInterval(interval_id);\n }\n },\n methods: {\n\n // チャンネルをピン留めする\n addPinnedChannel(channel_id: string) {\n\n // ピン留めするチャンネルの ID を追加 (保存は自動で行われる)\n this.settingsStore.settings.pinned_channel_ids.push(channel_id);\n\n const channel = this.channelsStore.getChannel(channel_id);\n this.$message.show(`${channel.channel_name}をピン留めしました。`);\n },\n\n // チャンネルをピン留めから外す\n removePinnedChannel(channel_id: string) {\n\n // ピン留めを外すチャンネルの ID を削除 (保存は自動で行われる)\n this.settingsStore.settings.pinned_channel_ids.splice(this.settingsStore.settings.pinned_channel_ids.indexOf(channel_id), 1);\n\n // この時点でピン留めされているチャンネルがないなら、タブを地デジタブに切り替える\n if (this.channelsStore.channels_list_with_pinned.get('ピン留め').length === 0) {\n this.tab = 1;\n }\n\n const channel = this.channelsStore.getChannel(channel_id);\n this.$message.show(`${channel.channel_name}のピン留めを外しました。`);\n },\n\n // チャンネルがピン留めされているか\n isPinnedChannel(channel_id: string): boolean {\n\n // 引数のチャンネルがピン留めリストに存在するかを返す\n return this.settingsStore.settings.pinned_channel_ids.includes(channel_id);\n }\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Home.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Home.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Home.vue?vue&type=template&id=5395b00e&scoped=true&\"\nimport script from \"./Home.vue?vue&type=script&lang=ts&\"\nexport * from \"./Home.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Home.vue?vue&type=style&index=0&id=5395b00e&prod&lang=scss&\"\nimport style1 from \"./Home.vue?vue&type=style&index=1&id=5395b00e&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"5395b00e\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"route-container\"},[_c('main',{staticClass:\"watch-container\",class:{\n 'watch-container--control-display': _vm.is_control_display,\n 'watch-container--panel-display': _vm.Utils.isSmartphoneVertical() ? true : _vm.is_panel_display,\n 'watch-container--fullscreen': _vm.is_fullscreen,\n }},[_c('nav',{staticClass:\"watch-navigation\",on:{\"mousemove\":function($event){return _vm.controlDisplayTimer($event)},\"touchmove\":function($event){return _vm.controlDisplayTimer($event)},\"click\":function($event){return _vm.controlDisplayTimer($event)}}},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"watch-navigation__icon\",attrs:{\"to\":\"/tv/\"}},[_c('img',{staticClass:\"watch-navigation__icon-image\",attrs:{\"src\":\"/assets/images/icon.svg\",\"width\":\"23px\"}})]),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.right\",value:('テレビをみる'),expression:\"'テレビをみる'\",modifiers:{\"right\":true}}],staticClass:\"watch-navigation__link\",attrs:{\"active-class\":\"watch-navigation__link--active\",\"to\":\"/tv/\"}},[_c('Icon',{staticClass:\"watch-navigation__link-icon\",attrs:{\"icon\":\"fluent:tv-20-regular\",\"width\":\"26px\"}})],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.right\",value:('ビデオをみる'),expression:\"'ビデオをみる'\",modifiers:{\"right\":true}}],staticClass:\"watch-navigation__link\",attrs:{\"active-class\":\"watch-navigation__link--active\",\"to\":\"/videos/\"}},[_c('Icon',{staticClass:\"watch-navigation__link-icon\",attrs:{\"icon\":\"fluent:movies-and-tv-20-regular\",\"width\":\"26px\"}})],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.right\",value:('番組表'),expression:\"'番組表'\",modifiers:{\"right\":true}}],staticClass:\"watch-navigation__link\",attrs:{\"active-class\":\"watch-navigation__link--active\",\"to\":\"/timetable/\"}},[_c('Icon',{staticClass:\"watch-navigation__link-icon\",attrs:{\"icon\":\"fluent:calendar-ltr-20-regular\",\"width\":\"26px\"}})],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.right\",value:('録画予約'),expression:\"'録画予約'\",modifiers:{\"right\":true}}],staticClass:\"watch-navigation__link\",attrs:{\"active-class\":\"watch-navigation__link--active\",\"to\":\"/reserves/\"}},[_c('Icon',{staticClass:\"watch-navigation__link-icon\",staticStyle:{\"padding\":\"0.5px\"},attrs:{\"icon\":\"fluent:timer-16-regular\",\"width\":\"26px\"}})],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.right\",value:('マイリスト'),expression:\"'マイリスト'\",modifiers:{\"right\":true}}],staticClass:\"watch-navigation__link\",attrs:{\"active-class\":\"watch-navigation__link--active\",\"to\":\"/mylist/\"}},[_c('Icon',{staticClass:\"watch-navigation__link-icon\",attrs:{\"icon\":\"ic:round-playlist-play\",\"width\":\"26px\"}})],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.right\",value:('キャプチャ'),expression:\"'キャプチャ'\",modifiers:{\"right\":true}}],staticClass:\"watch-navigation__link\",attrs:{\"active-class\":\"watch-navigation__link--active\",\"to\":\"/captures/\"}},[_c('Icon',{staticClass:\"watch-navigation__link-icon\",attrs:{\"icon\":\"fluent:image-multiple-24-regular\",\"width\":\"26px\"}})],1),_c('v-spacer'),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.right\",value:('設定'),expression:\"'設定'\",modifiers:{\"right\":true}}],staticClass:\"watch-navigation__link\",attrs:{\"active-class\":\"watch-navigation__link--active\",\"to\":\"/settings/\"}},[_c('Icon',{staticClass:\"watch-navigation__link-icon\",attrs:{\"icon\":\"fluent:settings-20-regular\",\"width\":\"26px\"}})],1)],1),_c('div',{staticClass:\"watch-content\",on:{\"mousemove\":function($event){return _vm.controlDisplayTimer($event, true)},\"touchmove\":function($event){return _vm.controlDisplayTimer($event, true)},\"click\":function($event){return _vm.controlDisplayTimer($event, true)}}},[_c('header',{staticClass:\"watch-header\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"watch-header__back-icon\",attrs:{\"to\":\"/tv/\"}},[_c('Icon',{attrs:{\"icon\":\"fluent:arrow-left-12-filled\",\"width\":\"25px\"}})],1),_c('img',{staticClass:\"watch-header__broadcaster\",attrs:{\"src\":`${_vm.Utils.api_base_url}/channels/${(_vm.channelsStore.channel_id)}/logo`}}),_c('span',{staticClass:\"watch-header__program-title\",domProps:{\"innerHTML\":_vm._s(_vm.ProgramUtils.decorateProgramInfo(_vm.channelsStore.channel.current.program_present, 'title'))}}),_c('span',{staticClass:\"watch-header__program-time\"},[_vm._v(\" \"+_vm._s(_vm.ProgramUtils.getProgramTime(_vm.channelsStore.channel.current.program_present, true))+\" \")]),_c('v-spacer'),_c('span',{staticClass:\"watch-header__now\"},[_vm._v(_vm._s(_vm.time))])],1),_c('div',{staticClass:\"watch-player\",class:{\n 'watch-player--loading': _vm.is_loading,\n 'watch-player--virtual-keyboard-display': _vm.is_virtual_keyboard_display && _vm.Utils.hasActiveElementClass('dplayer-comment-input'),\n }},[_c('div',{staticClass:\"watch-player__background-wrapper\"},[_c('div',{staticClass:\"watch-player__background\",class:{'watch-player__background--display': _vm.is_background_display},style:({backgroundImage: `url(${_vm.background_url})`})},[_c('img',{staticClass:\"watch-player__background-logo\",attrs:{\"src\":\"/assets/images/logo.svg\"}})])]),_c('v-progress-circular',{staticClass:\"watch-player__buffering\",class:{'watch-player__buffering--display': _vm.is_video_buffering && (_vm.is_loading || (_vm.player !== null && !_vm.player.video.paused))},attrs:{\"indeterminate\":\"\",\"size\":\"60\",\"width\":\"6\"}}),_c('div',{staticClass:\"watch-player__dplayer\"}),_c('div',{staticClass:\"watch-player__button\",on:{\"mousemove\":function($event){return _vm.controlDisplayTimer($event)},\"touchmove\":function($event){return _vm.controlDisplayTimer($event)},\"click\":function($event){return _vm.controlDisplayTimer($event)}}},[_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.top\",value:('前のチャンネル'),expression:\"'前のチャンネル'\",modifiers:{\"top\":true}}],staticClass:\"switch-button switch-button-up\",on:{\"click\":function($event){_vm.is_zapping = true; _vm.$router.push({path: `/tv/watch/${_vm.channelsStore.channel.previous.channel_id}`})}}},[_c('Icon',{staticClass:\"switch-button-icon\",attrs:{\"icon\":\"fluent:ios-arrow-left-24-filled\",\"width\":\"32px\",\"rotate\":\"1\"}})],1),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"switch-button switch-button-panel switch-button-panel--open\",on:{\"click\":function($event){_vm.is_panel_display = !_vm.is_panel_display}}},[_c('Icon',{staticClass:\"switch-button-icon\",attrs:{\"icon\":\"fluent:navigation-16-filled\",\"width\":\"32px\"}})],1),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.bottom\",value:('次のチャンネル'),expression:\"'次のチャンネル'\",modifiers:{\"bottom\":true}}],staticClass:\"switch-button switch-button-down\",on:{\"click\":function($event){_vm.is_zapping = true; _vm.$router.push({path: `/tv/watch/${_vm.channelsStore.channel.next.channel_id}`})}}},[_c('Icon',{staticClass:\"switch-button-icon\",attrs:{\"icon\":\"fluent:ios-arrow-right-24-filled\",\"width\":\"33px\",\"rotate\":\"1\"}})],1)])],1)]),_c('div',{staticClass:\"watch-panel\",on:{\"mousemove\":function($event){return _vm.controlDisplayTimer($event)}}},[_c('div',{staticClass:\"watch-panel__header\"},[_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"panel-close-button\",on:{\"click\":function($event){_vm.is_panel_display = false}}},[_c('Icon',{staticClass:\"panel-close-button__icon\",attrs:{\"icon\":\"akar-icons:chevron-right\",\"width\":\"25px\"}}),_c('span',{staticClass:\"panel-close-button__text\"},[_vm._v(\"閉じる\")])],1),_c('v-spacer'),_c('div',{staticClass:\"panel-broadcaster\"},[_c('img',{staticClass:\"panel-broadcaster__icon\",attrs:{\"src\":`${_vm.Utils.api_base_url}/channels/${(_vm.channelsStore.channel_id)}/logo`}}),_c('div',{staticClass:\"panel-broadcaster__number\"},[_vm._v(_vm._s(_vm.channelsStore.channel.current.channel_number))]),_c('div',{staticClass:\"panel-broadcaster__name\"},[_vm._v(_vm._s(_vm.channelsStore.channel.current.channel_name))])])],1),_c('div',{staticClass:\"watch-panel__content-container\"},[_c('Program',{staticClass:\"watch-panel__content\",class:{'watch-panel__content--active': _vm.tv_panel_active_tab === 'Program'}}),_c('Channel',{staticClass:\"watch-panel__content\",class:{'watch-panel__content--active': _vm.tv_panel_active_tab === 'Channel'}}),_c('Comment',{ref:\"Comment\",staticClass:\"watch-panel__content\",class:{'watch-panel__content--active': _vm.tv_panel_active_tab === 'Comment'},attrs:{\"channel\":_vm.channelsStore.channel.current,\"player\":_vm.player}}),_c('Twitter',{ref:\"Twitter\",staticClass:\"watch-panel__content\",class:{'watch-panel__content--active': _vm.tv_panel_active_tab === 'Twitter'},attrs:{\"player\":_vm.player,\"is_virtual_keyboard_display\":_vm.is_virtual_keyboard_display},on:{\"panel_folding_requested\":function($event){_vm.is_panel_display = false}}})],1),_c('div',{staticClass:\"watch-panel__navigation\"},[_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"panel-navigation-button\",class:{'panel-navigation-button--active': _vm.tv_panel_active_tab === 'Program'},on:{\"click\":function($event){_vm.tv_panel_active_tab = 'Program'}}},[_c('Icon',{staticClass:\"panel-navigation-button__icon\",attrs:{\"icon\":\"fa-solid:info-circle\",\"width\":\"33px\"}}),_c('span',{staticClass:\"panel-navigation-button__text\"},[_vm._v(\"番組情報\")])],1),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"panel-navigation-button\",class:{'panel-navigation-button--active': _vm.tv_panel_active_tab === 'Channel'},on:{\"click\":function($event){_vm.tv_panel_active_tab = 'Channel'}}},[_c('Icon',{staticClass:\"panel-navigation-button__icon\",attrs:{\"icon\":\"fa-solid:broadcast-tower\",\"width\":\"34px\"}}),_c('span',{staticClass:\"panel-navigation-button__text\"},[_vm._v(\"チャンネル\")])],1),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"panel-navigation-button\",class:{'panel-navigation-button--active': _vm.tv_panel_active_tab === 'Comment'},on:{\"click\":function($event){_vm.tv_panel_active_tab = 'Comment'}}},[_c('Icon',{staticClass:\"panel-navigation-button__icon\",attrs:{\"icon\":\"bi:chat-left-text-fill\",\"width\":\"29px\"}}),_c('span',{staticClass:\"panel-navigation-button__text\"},[_vm._v(\"コメント\")])],1),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"panel-navigation-button\",class:{'panel-navigation-button--active': _vm.tv_panel_active_tab === 'Twitter'},on:{\"click\":function($event){_vm.tv_panel_active_tab = 'Twitter'}}},[_c('Icon',{staticClass:\"panel-navigation-button__icon\",attrs:{\"icon\":\"fa-brands:twitter\",\"width\":\"34px\"}}),_c('span',{staticClass:\"panel-navigation-button__text\"},[_vm._v(\"Twitter\")])],1)])])]),_c('v-dialog',{attrs:{\"max-width\":\"1050\",\"transition\":\"slide-y-transition\"},model:{value:(_vm.shortcut_key_modal),callback:function ($$v) {_vm.shortcut_key_modal=$$v},expression:\"shortcut_key_modal\"}},[_c('v-card',[_c('v-card-title',{staticClass:\"px-5 pt-4 pb-3 d-flex align-center font-weight-bold\"},[_c('Icon',{attrs:{\"icon\":\"fluent:keyboard-20-filled\",\"height\":\"28px\"}}),_c('span',{staticClass:\"ml-3\"},[_vm._v(\"キーボードショートカット\")]),_c('v-spacer'),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"d-flex align-center rounded-circle cursor-pointer px-2 py-2\",on:{\"click\":function($event){_vm.shortcut_key_modal = false}}},[_c('Icon',{attrs:{\"icon\":\"fluent:dismiss-12-filled\",\"width\":\"23px\",\"height\":\"23px\"}})],1)],1),_c('div',{staticClass:\"px-5 pb-4\"},[_c('v-row',_vm._l((_vm.shortcut_key_list),function(shortcut_key_column,shortcut_key_column_name){return _c('v-col',{key:shortcut_key_column_name,attrs:{\"cols\":\"6\"}},_vm._l((shortcut_key_column),function(shortcut_keys){return _c('div',{key:shortcut_keys.name,staticClass:\"mt-3\"},[_c('div',{staticClass:\"text-subtitle-1 d-flex align-center font-weight-bold\"},[_c('Icon',{attrs:{\"icon\":shortcut_keys.icon,\"height\":shortcut_keys.icon_height}}),_c('span',{staticClass:\"ml-2\"},[_vm._v(_vm._s(shortcut_keys.name))])],1),_vm._l((shortcut_keys.shortcuts),function(shortcut){return _c('div',{key:shortcut.name,staticClass:\"mt-3\"},[_c('div',{staticClass:\"text-subtitle-2 mt-2 d-flex align-center font-weight-medium\"},[_c('span',{staticClass:\"mr-2\",domProps:{\"innerHTML\":_vm._s(shortcut.name)}}),_c('div',{staticClass:\"ml-auto d-flex align-center flex-shrink-0\"},_vm._l((shortcut.keys),function(key,index){return _c('div',{key:key.name,staticClass:\"ml-auto d-flex align-center\"},[_c('span',{staticClass:\"shortcut-key\"},[_vm._l((key.name.split(';')),function(key_name){return _c('Icon',{directives:[{name:\"show\",rawName:\"v-show\",value:(key.icon === true),expression:\"key.icon === true\"}],key:key_name,attrs:{\"icon\":key_name,\"height\":\"18px\"}})}),(key.icon === false)?_c('span',{domProps:{\"innerHTML\":_vm._s(key.name)}}):_vm._e()],2),(index < (shortcut.keys.length - 1))?_c('span',{staticClass:\"shortcut-key-plus\"},[_vm._v(\"+\")]):_vm._e()])}),0)])])})],2)}),0)}),1)],1)],1)],1),_c('BottomNavigation')],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"channels-container channels-container--watch\"},[_c('v-tabs-fix',{staticClass:\"channels-tab\",attrs:{\"centered\":\"\",\"show-arrows\":\"\"},model:{value:(_vm.tab),callback:function ($$v) {_vm.tab=$$v},expression:\"tab\"}},_vm._l((Array.from(_vm.channelsStore.channels_list_with_pinned_for_watch)),function([channels_type,]){return _c('v-tab',{key:channels_type,staticClass:\"channels-tab__item\"},[_vm._v(\" \"+_vm._s(channels_type)+\" \")])}),1),_c('div',{staticClass:\"channels-list-container\"},[_c('v-tabs-items-fix',{staticClass:\"channels-list\",model:{value:(_vm.tab),callback:function ($$v) {_vm.tab=$$v},expression:\"tab\"}},_vm._l((Array.from(_vm.channelsStore.channels_list_with_pinned_for_watch)),function([channels_type, channels]){return _c('v-tab-item-fix',{key:channels_type,staticClass:\"channels\"},_vm._l((channels),function(channel){return _c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],key:channel.id,staticClass:\"channel\",attrs:{\"to\":`/tv/watch/${channel.channel_id}`}},[_c('div',{staticClass:\"channel__broadcaster\"},[_c('img',{staticClass:\"channel__broadcaster-icon\",attrs:{\"src\":`${_vm.Utils.api_base_url}/channels/${channel.channel_id}/logo`}}),_c('div',{staticClass:\"channel__broadcaster-content\"},[_c('span',{staticClass:\"channel__broadcaster-name\"},[_vm._v(\"Ch: \"+_vm._s(channel.channel_number)+\" \"+_vm._s(channel.channel_name))]),_c('div',{staticClass:\"channel__broadcaster-force\",class:`channel__broadcaster-force--${_vm.ChannelUtils.getChannelForceType(channel.channel_force)}`},[_c('Icon',{attrs:{\"icon\":\"fa-solid:fire-alt\",\"height\":\"11px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(_vm._s(_vm.ProgramUtils.getAttribute(channel, 'channel_force', '-')))])],1)])]),_c('div',{staticClass:\"channel__program-present\"},[_c('span',{staticClass:\"channel__program-present-title\",domProps:{\"innerHTML\":_vm._s(_vm.ProgramUtils.decorateProgramInfo(channel.program_present, 'title'))}}),_c('span',{staticClass:\"channel__program-present-time\"},[_vm._v(_vm._s(_vm.ProgramUtils.getProgramTime(channel.program_present)))])]),_c('div',{staticClass:\"channel__program-following\"},[_c('div',{staticClass:\"channel__program-following-title\"},[_c('span',{staticClass:\"channel__program-following-title-decorate\"},[_vm._v(\"NEXT\")]),_c('Icon',{staticClass:\"channel__program-following-title-icon\",attrs:{\"icon\":\"fluent:fast-forward-20-filled\",\"width\":\"16px\"}}),_c('span',{staticClass:\"channel__program-following-title-text\",domProps:{\"innerHTML\":_vm._s(_vm.ProgramUtils.decorateProgramInfo(channel.program_following, 'title'))}})],1),_c('span',{staticClass:\"channel__program-following-time\"},[_vm._v(_vm._s(_vm.ProgramUtils.getProgramTime(channel.program_following)))])]),_c('div',{staticClass:\"channel__progressbar\"},[_c('div',{staticClass:\"channel__progressbar-progress\",style:(`width:${_vm.ProgramUtils.getProgramProgress(channel.program_present)}%;`)})])])}),1)}),1)],1)],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport useChannelsStore from '@/store/ChannelsStore';\nimport Utils, { ChannelUtils, ProgramUtils } from '@/utils';\n\nexport default Vue.extend({\n name: 'Panel-ChannelTab',\n data() {\n return {\n\n // ユーティリティをテンプレートで使えるように\n Utils: Utils,\n ChannelUtils: ChannelUtils,\n ProgramUtils: ProgramUtils,\n\n // タブの状態管理\n tab: null,\n };\n },\n computed: {\n // ChannelsStore に this.channelsStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useChannelsStore),\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Channel.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Channel.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Channel.vue?vue&type=template&id=3b3e1928&scoped=true&\"\nimport script from \"./Channel.vue?vue&type=script&lang=ts&\"\nexport * from \"./Channel.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Channel.vue?vue&type=style&index=0&id=3b3e1928&prod&lang=scss&\"\nimport style1 from \"./Channel.vue?vue&type=style&index=1&id=3b3e1928&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"3b3e1928\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"comment-container\"},[_c('section',{staticClass:\"comment-header\"},[_c('h2',{staticClass:\"comment-header__title\"},[_c('Icon',{staticClass:\"comment-header__title-icon\",attrs:{\"icon\":\"bi:chat-left-text-fill\",\"height\":\"18.5px\"}}),_c('span',{staticClass:\"comment-header__title-text\"},[_vm._v(\"コメント\")])],1),_c('button',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"comment-header__button ml-auto\",on:{\"click\":function($event){_vm.comment_mute_settings_modal = !_vm.comment_mute_settings_modal}}},[_c('Icon',{attrs:{\"icon\":\"heroicons-solid:filter\",\"height\":\"11px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(\"ミュート設定\")])],1)]),_c('section',{ref:\"comment_list_wrapper\",staticClass:\"comment-list-wrapper\"},[_c('div',{staticClass:\"comment-list-dropdown\",class:{'comment-list-dropdown--display': _vm.is_comment_list_dropdown_display},style:({'--comment-list-dropdown-top': `${_vm.comment_list_dropdown_top}px`})},[_c('v-list',{staticStyle:{\"background\":\"var(--v-background-lighten1)\"}},[_c('v-list-item',{staticStyle:{\"min-height\":\"30px\"},attrs:{\"dense\":\"\"},on:{\"click\":function($event){return _vm.addMutedKeywords()}}},[_c('v-list-item-title',{staticClass:\"d-flex align-center\"},[_c('Icon',{attrs:{\"icon\":\"fluent:comment-dismiss-20-filled\",\"width\":\"20px\"}}),_c('span',{staticClass:\"ml-2\"},[_vm._v(\"このコメントをミュート\")])],1)],1),_c('v-list-item',{staticStyle:{\"min-height\":\"30px\"},attrs:{\"dense\":\"\"},on:{\"click\":function($event){return _vm.addMutedNiconicoUserIds()}}},[_c('v-list-item-title',{staticClass:\"d-flex align-center\"},[_c('Icon',{attrs:{\"icon\":\"fluent:person-prohibited-20-filled\",\"width\":\"20px\"}}),_c('span',{staticClass:\"ml-2\"},[_vm._v(\"このコメントの投稿者をミュート\")])],1)],1)],1)],1),_c('div',{staticClass:\"comment-list-cover\",class:{'comment-list-cover--display': _vm.is_comment_list_dropdown_display},on:{\"click\":function($event){return _vm.hideCommentListDropdown()}}}),_c('DynamicScroller',{staticClass:\"comment-list\",attrs:{\"direction\":'vertical',\"items\":_vm.comment_list,\"min-item-size\":34},scopedSlots:_vm._u([{key:\"default\",fn:function({item, active}){return [_c('DynamicScrollerItem',{attrs:{\"item\":item,\"active\":active,\"size-dependencies\":[item.text]}},[_c('div',{staticClass:\"comment\",class:{'comment--my-post': item.my_post}},[_c('span',{staticClass:\"comment__text\"},[_vm._v(_vm._s(item.text))]),_c('span',{staticClass:\"comment__time\"},[_vm._v(_vm._s(item.time))]),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\",value:(!_vm.Utils.isTouchDevice()),expression:\"!Utils.isTouchDevice()\"}],staticClass:\"comment__icon\",on:{\"mouseup\":function($event){return _vm.showCommentListDropdown($event, item)},\"touchend\":function($event){return _vm.showCommentListDropdown($event, item)}}},[_c('Icon',{attrs:{\"icon\":\"fluent:more-vertical-20-filled\",\"width\":\"20px\"}})],1)])])]}}])}),(_vm.initialize_failed_message === null && _vm.comment_list.length === 0)?_c('div',{staticClass:\"comment-announce\"},[_c('div',{staticClass:\"comment-announce__heading\"},[_vm._v(\"まだコメントがありません。\")]),_vm._m(0)]):_vm._e(),(_vm.initialize_failed_message !== null && _vm.comment_list.length === 0)?_c('div',{staticClass:\"comment-announce\"},[_c('div',{staticClass:\"comment-announce__heading\"},[_vm._v(\"コメントがありません。\")]),_c('div',{staticClass:\"comment-announce__text\"},[_c('p',{staticClass:\"mt-0 mb-0\"},[_vm._v(_vm._s(_vm.initialize_failed_message))])])]):_vm._e()],1),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"comment-scroll-button elevation-5\",class:{'comment-scroll-button--display': _vm.is_manual_scroll},on:{\"click\":function($event){_vm.is_manual_scroll = false; _vm.scrollCommentList(true);}}},[_c('Icon',{attrs:{\"icon\":\"fluent:arrow-down-12-filled\",\"height\":\"29px\"}})],1),_c('CommentMuteSettings',{model:{value:(_vm.comment_mute_settings_modal),callback:function ($$v) {_vm.comment_mute_settings_modal=$$v},expression:\"comment_mute_settings_modal\"}})],1)\n}\nvar staticRenderFns = [function (){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"comment-announce__text\"},[_c('p',{staticClass:\"mt-0 mb-0\"},[_vm._v(\"このチャンネルに対応するニコニコ実況のコメントが、リアルタイムで表示されます。\")])])\n}]\n\nexport { render, staticRenderFns }","\nimport dayjs from 'dayjs';\nimport DPlayer from 'dplayer';\nimport * as DPlayerType from 'dplayer/dist/d.ts/types/DPlayer';\n\nimport Channels from '@/services/Channels';\nimport Utils, { CommentUtils } from '@/utils';\n\n\nexport interface ICommentData {\n id: number;\n text: string;\n time: string;\n user_id: string;\n my_post: boolean;\n}\n\ninterface IWatchSessionResult {\n is_success: boolean;\n detail: string;\n message_server_url?: string;\n thread_id?: string;\n your_post_key?: string;\n}\n\n\nclass LiveCommentManager {\n\n // 視聴セッションの WebSocket のインスタンス\n private watch_session: WebSocket | null = null;\n // コメントセッションの WebSocket のインスタンス\n private comment_session: WebSocket | null = null;\n // vpos を計算する基準となる時刻のタイムスタンプ\n private vpos_base_timestamp: number = 0;\n // 座席維持用のタイマーのインターバル ID\n private keep_seat_interval_id: number | null = null;\n // destroy() 時に EventListener を全解除するための AbortController\n private abort_controller: AbortController = new AbortController();\n\n private player: DPlayer;\n private channel_id: string;\n private on_initial_comments_received: (initial_comments: ICommentData[]) => void;\n private on_comment_received: (comment: ICommentData) => void;\n\n constructor(\n player: DPlayer,\n channel_id: string,\n on_initial_comments_received: (initial_comments: ICommentData[]) => void,\n on_comment_received: (comment: ICommentData) => void,\n ) {\n this.player = player;\n this.channel_id = channel_id;\n this.on_initial_comments_received = on_initial_comments_received;\n this.on_comment_received = on_comment_received;\n }\n\n\n /**\n * ニコニコ実況に接続し、セッションを初期化する\n * 初期化に成功した場合は、随時コールバックにニコニコ実況から受信したコメントが渡される\n * @returns セッションの初期化に成功したかどうか\n */\n public async initSession(): Promise<{\n is_success: boolean;\n detail: string;\n }> {\n\n // 視聴セッションを初期化\n const watch_session_result = await this.initWatchSession();\n if (watch_session_result.is_success === false) {\n return {\n is_success: false,\n detail: watch_session_result.detail,\n };\n }\n\n // 視聴セッションを初期化できた場合のみ、\n // 取得したコメントサーバーへの接続情報を使い、非同期でコメントセッションを初期化\n this.initCommentSession(watch_session_result);\n\n return {\n is_success: true,\n detail: '視聴セッションを初期化しました。',\n };\n }\n\n\n /**\n * 視聴セッションを初期化する\n * @returns コメントサーバーへの接続情報 or エラー情報\n */\n private async initWatchSession(): Promise {\n\n // サーバーから disconnect メッセージが送られてきた際のフラグ\n let is_disconnect_message_received = false;\n\n // セッション情報を取得\n const watch_session_info = await Channels.fetchJikkyoSession(this.channel_id);\n if (watch_session_info === null) {\n return {\n is_success: false,\n detail: 'ニコニコ実況のセッション情報を取得できませんでした。',\n };\n }\n if (watch_session_info.is_success === false) {\n console.error(`[LiveCommentManager][WatchSession] Error: ${watch_session_info.detail}`);\n // 通常発生しないエラーメッセージ (サーバーエラーなど) はプレイヤー側にも通知する\n if ((watch_session_info.detail !== 'このチャンネルはニコニコ実況に対応していません。') &&\n (watch_session_info.detail !== '現在放送中のニコニコ実況がありません。')) {\n this.player.notice(watch_session_info.detail);\n }\n return {\n is_success: false,\n detail: watch_session_info.detail,\n };\n }\n\n // 視聴セッション WebSocket を開く\n this.watch_session = new WebSocket(watch_session_info.audience_token);\n\n // 視聴セッションの接続が開かれたとき\n this.watch_session.addEventListener('open', () => {\n\n // 視聴セッションをリクエスト\n // 公式ドキュメントいわく、stream フィールドは Optional らしい\n // サーバー負荷軽減のため、映像が不要な場合は必ず省略してくださいとのこと\n this.watch_session?.send(JSON.stringify({\n type: 'startWatching',\n data: {\n 'reconnect': false,\n },\n }));\n\n }, { signal: this.abort_controller.signal });\n\n // 視聴セッションの接続が閉じられたとき(ネットワークが切断された場合など)\n this.watch_session.addEventListener('close', async (event) => {\n\n // すでに disconnect メッセージが送られてきている場合は何もしない\n if (is_disconnect_message_received === true) {\n return;\n }\n\n // 接続切断の理由を表示\n console.error(`[LiveCommentManager][WatchSession] Connection closed. (Code: ${event.code})`);\n this.player.notice(`ニコニコ実況との接続が切断されました。(Code: ${event.code})`);\n\n // 10 秒ほど待ってから再接続する\n // ニコ生側から切断された場合と異なりネットワークが切断された可能性が高いので、間を多めに取る\n await Utils.sleep(10);\n await this.reconnect();\n\n }, { signal: this.abort_controller.signal });\n\n // 視聴セッション WebSocket からメッセージを受信したとき\n // 視聴セッションはコメント送信時のために維持し続ける必要がある\n // 以下はいずれも視聴セッションを維持し続けたり、エラーが発生した際に再接続するための処理\n this.watch_session.addEventListener('message', async (event) => {\n\n // 各メッセージタイプに対応する処理を実行\n const message = JSON.parse(event.data);\n switch (message.type) {\n\n // 座席情報\n case 'seat': {\n // すでにタイマーが設定されている場合は何もしない\n if (this.keep_seat_interval_id !== null) {\n break;\n }\n // keepIntervalSec の秒数ごとに keepSeat を送信して座席を維持する\n this.keep_seat_interval_id = window.setInterval(() => {\n if (this.watch_session && this.watch_session.readyState === WebSocket.OPEN) {\n // セッションがまだ開いていれば、座席を維持する\n this.watch_session.send(JSON.stringify({type: 'keepSeat'}));\n } else {\n // セッションが閉じられている場合は、タイマーを停止する\n window.clearInterval(this.keep_seat_interval_id ?? 0);\n }\n }, message.data.keepIntervalSec * 1000);\n break;\n }\n\n // ping-pong\n case 'ping': {\n // pong を返してセッションを維持する\n // 送り返さなかった場合、勝手にセッションが閉じられてしまう\n this.watch_session.send(JSON.stringify({type: 'pong'}));\n break;\n }\n\n // エラー情報\n case 'error': {\n // COMMENT_POST_NOT_ALLOWED と INVALID_MESSAGE に関しては sendComment() の方で処理するので、ここでは何もしない\n if (message.data.code === 'COMMENT_POST_NOT_ALLOWED' || message.data.code === 'INVALID_MESSAGE') {\n break;\n }\n\n let error = `ニコニコ実況でエラーが発生しています。(Code: ${message.data.code})`;\n switch (message.data.code) {\n case 'CONNECT_ERROR':\n error = 'ニコニコ実況のコメントサーバーに接続できません。';\n break;\n case 'CONTENT_NOT_READY':\n error = 'ニコニコ実況が配信できない状態です。';\n break;\n case 'NO_THREAD_AVAILABLE':\n error = 'ニコニコ実況のコメントスレッドを取得できません。';\n break;\n case 'NO_ROOM_AVAILABLE':\n error = 'ニコニコ実況のコメント部屋を取得できません。';\n break;\n case 'NO_PERMISSION':\n error = 'ニコニコ実況の API にアクセスする権限がありません。';\n break;\n case 'NOT_ON_AIR':\n error = 'ニコニコ実況が放送中ではありません。';\n break;\n case 'BROADCAST_NOT_FOUND':\n error = 'ニコニコ実況の配信情報を取得できません。';\n break;\n case 'INTERNAL_SERVERERROR':\n error = 'ニコニコ実況でサーバーエラーが発生しています。';\n break;\n }\n\n // エラー情報を表示\n console.error(`[LiveCommentManager][WatchSession] Error occurred. (Code: ${message.data.code})`);\n this.player.notice(error);\n\n // 5 秒ほど待ってから再接続する\n await Utils.sleep(5);\n await this.reconnect();\n break;\n }\n\n // 再接続を求められた\n case 'reconnect': {\n // waitTimeSec に記載の秒数だけ待ってから再接続する\n // 公式ドキュメントには reconnect で送られてくる audienceToken で再接続しろと書いてあるんだけど、\n // 確実性的な面で実装が面倒なので当面このままにしておく\n await this.reconnect();\n break;\n }\n\n // 視聴セッションが閉じられた(4時のリセットなど)\n case 'disconnect': {\n // 実際に接続が閉じられる前に disconnect メッセージが送られてきたので、\n // WebSocket の close メッセージを実行させないようにする\n is_disconnect_message_received = true;\n\n // 接続切断の理由\n let disconnect_reason = `ニコニコ実況との接続が切断されました。(${message.data.reason})`;\n switch (message.data.reason) {\n case 'TAKEOVER':\n disconnect_reason = 'ニコニコ実況の番組から追い出されました。';\n break;\n case 'NO_PERMISSION':\n disconnect_reason = 'ニコニコ実況の番組の座席を取得できませんでした。';\n break;\n case 'END_PROGRAM':\n disconnect_reason = 'ニコニコ実況がリセットされたか、コミュニティの番組が終了しました。';\n break;\n case 'PING_TIMEOUT':\n disconnect_reason = 'コメントサーバーとの接続生存確認に失敗しました。';\n break;\n case 'TOO_MANY_CONNECTIONS':\n disconnect_reason = 'ニコニコ実況の同一ユーザからの接続数上限を越えています。';\n break;\n case 'TOO_MANY_WATCHINGS':\n disconnect_reason = 'ニコニコ実況の同一ユーザからの視聴番組数上限を越えています。';\n break;\n case 'CROWDED':\n disconnect_reason = 'ニコニコ実況の番組が満席です。';\n break;\n case 'MAINTENANCE_IN':\n disconnect_reason = 'ニコニコ実況はメンテナンス中です。';\n break;\n case 'SERVICE_TEMPORARILY_UNAVAILABLE':\n disconnect_reason = 'ニコニコ実況で一時的にサーバーエラーが発生しています。';\n break;\n }\n\n // 接続切断の理由を表示\n console.error(`[LiveCommentManager][WatchSession] Disconnected. (Reason: ${message.data.reason})`);\n this.player.notice(disconnect_reason);\n\n // 5 秒ほど待ってから再接続する\n await Utils.sleep(5);\n await this.reconnect();\n break;\n }\n }\n\n }, { signal: this.abort_controller.signal });\n\n // コメントサーバーへの接続情報を返す\n // イベント内で値を返すため、Promise で包む\n return new Promise((resolve) => {\n this.watch_session.addEventListener('message', async (event) => {\n const message = JSON.parse(event.data);\n if (message.type === 'room') {\n\n // vpos の基準時刻のタイムスタンプを取得 (ミリ秒単位)\n // vpos は番組開始時間からの累計秒数\n this.vpos_base_timestamp = dayjs(message.data.vposBaseTime).valueOf();\n\n // コメントサーバーへの接続情報を返す\n console.log(`[LiveCommentManager][WatchSession] Connected.\\nThread ID: ${message.data.threadId}\\n`);\n return resolve({\n is_success: true,\n detail: '視聴セッションを取得しました。',\n // コメントサーバーへの接続情報\n message_server_url: message.data.messageServer.uri,\n // コメントサーバー上のスレッド ID\n thread_id: message.data.threadId,\n // メッセージサーバーから受信するコメント (chat メッセージ) に yourpost フラグを付けるためのキー\n your_post_key: (message.data.yourPostKey ? message.data.yourPostKey : null),\n });\n }\n }, { signal: this.abort_controller.signal });\n });\n }\n\n\n /**\n * コメントセッションを初期化する\n * @param comment_session_info コメントサーバーへの接続情報\n */\n private initCommentSession(comment_session_info: IWatchSessionResult): void {\n\n // 初回にドカッと送信されてくる過去コメントを受信し終えるまで格納するバッファ\n const initial_comments_buffer: ICommentData[] = [];\n\n // 初回にドカッと送信されてくる過去コメントを受信し終えたかどうかのフラグ\n let initial_comments_received = false;\n\n // コメントセッション WebSocket を開く\n this.comment_session = new WebSocket(comment_session_info.message_server_url);\n\n // コメントセッション WebSocket を開いたとき\n this.comment_session.addEventListener('open', () => {\n\n // コメント送信をリクエスト\n // このコマンドを送らないとコメントが送信されてこない\n this.comment_session.send(JSON.stringify([\n {ping: {content: 'rs:0'}},\n {ping: {content: 'ps:0'}},\n {\n thread: {\n version: '20061206', // 設定必須\n thread: comment_session_info.thread_id, // スレッド ID\n threadkey: comment_session_info.your_post_key, // スレッドキー\n user_id: '', // ユーザー ID(設定不要らしい)\n res_from: -50, // 最初にコメントを 50 個送信する\n }\n },\n {ping: {content: 'pf:0'}},\n {ping: {content: 'rf:0'}},\n ]));\n\n }, { signal: this.abort_controller.signal });\n\n // コメントセッション WebSocket からメッセージを受信したとき\n this.comment_session.addEventListener('message', async (event) => {\n\n // メッセージを取得\n const message = JSON.parse(event.data);\n\n // 接続失敗\n if (message.thread !== undefined) {\n if (message.thread.resultcode !== 0) {\n console.error(`[LiveCommentManager][CommentSession] Connection failed. (Code: ${message.thread.resultcode})`);\n return;\n }\n }\n\n // ping メッセージのみ\n // rf:0 が送られてきたら初回にドカッと送信されてくる過去コメントの受信は完了\n // この時点で初回コメントを一気にコールバックに送る\n if (message.ping !== undefined && message.ping.content === 'rf:0') {\n initial_comments_received = true;\n this.on_initial_comments_received(initial_comments_buffer);\n return;\n }\n\n // コメントデータを取得\n const comment = message.chat;\n\n // コメントデータが不正な場合 or 自分のコメントの場合は弾く\n if ((comment === undefined || comment.content === undefined || comment.content === '') ||\n (comment.yourpost && comment.yourpost === 1)) {\n return;\n }\n\n // コメントコマンドをパース\n const { color, position, size } = CommentUtils.parseCommentCommand(comment.mail);\n\n // ミュート対象のコメントかどうかを判定し、もしそうならここで弾く\n if (CommentUtils.isMutedComment(comment.content, comment.user_id, color, position, size)) {\n return;\n }\n\n // コメントリストへ追加するオブジェクト\n const comment_data: ICommentData = {\n id: comment.no,\n text: comment.content,\n time: dayjs(comment.date * 1000).format('HH:mm:ss'),\n user_id: comment.user_id,\n my_post: false,\n };\n\n // もしまだ初回コメントを受信し終えていないなら、バッファに格納して終了\n // 初回コメントはプレイヤーには描画しないため問題ない\n if (initial_comments_received === false) {\n initial_comments_buffer.push(comment_data);\n return;\n }\n\n // 配信で発生する遅延分待ってから\n // おおよその遅延時間は video.buffered.end(0) - video.currentTime で取得できる\n let buffered_end = 0;\n if (this.player.video.buffered.length >= 1) {\n buffered_end = this.player.video.buffered.end(0);\n }\n const comment_delay_time = buffered_end - this.player.video.currentTime;\n // console.log(`[LiveCommentManager][CommentSession] Delay: ${comment_delay_time} sec.`)\n await Utils.sleep(comment_delay_time);\n\n // コールバック関数を実行\n this.on_comment_received(comment_data);\n\n // プレイヤーにコメントを描画する (映像再生時のみ)\n if (this.player.video.paused === false) {\n this.player.danmaku.draw({\n text: comment.content,\n color: color,\n type: position,\n size: size,\n });\n }\n\n }, { signal: this.abort_controller.signal });\n }\n\n\n /**\n * ニコニコ実況にコメントを送信する\n * @param options DPlayer のコメントオプション\n */\n public sendComment(options: DPlayerType.APIBackendSendOptions): void {\n\n // DPlayer 上のコメント色(カラーコード)とニコニコの色コマンド定義のマッピング\n const color_table = {\n '#FFEAEA': 'white',\n '#F02840': 'red',\n '#FD7E80': 'pink',\n '#FDA708': 'orange',\n '#FFE133': 'yellow',\n '#64DD17': 'green',\n '#00D4F5': 'cyan',\n '#4763FF': 'blue',\n };\n\n // DPlayer 上のコメント位置を表す値とニコニコの位置コマンド定義のマッピング\n const position_table = {\n 'top': 'ue',\n 'right': 'naka',\n 'bottom': 'shita',\n };\n\n // vpos を計算 (10ミリ秒単位)\n // 番組開始時間からの累計秒らしいけど、なぜ指定しないといけないのかは不明\n // 小数点以下は丸めないとコメントサーバー側で投稿エラーになる\n const vpos = Math.round((dayjs().valueOf() - this.vpos_base_timestamp) / 10);\n\n // 視聴セッションが null か、接続が切れている場合は弾く\n if (this.watch_session === null || this.watch_session.readyState !== WebSocket.OPEN) {\n console.error('[LiveCommentManager][WatchSession] Comment sending failed. (Connection is not established.)');\n options.error('コメントの送信に失敗しました。WebSocket 接続が確立されていません。');\n return;\n }\n\n // コメントを送信\n this.watch_session.send(JSON.stringify({\n 'type': 'postComment',\n 'data': {\n // コメント本文\n 'text': options.data.text,\n // コメントの色\n 'color': color_table[options.data.color.toUpperCase()],\n // コメント位置\n 'position': position_table[options.data.type],\n // コメントサイズ (DPlayer とニコニコで表現が共通なため、変換不要)\n 'size': options.data.size,\n // 番組開始時間からの累計秒 (10ミリ秒単位)\n 'vpos': vpos,\n // 匿名コメント (184) にするかどうか\n 'isAnonymous': true,\n }\n }));\n\n // コメント送信のレスポンスを取得\n const abort_controller = new AbortController();\n this.watch_session.addEventListener('message', (event) => {\n const message = JSON.parse(event.data);\n switch (message.type) {\n\n // postCommentResult が送られてきた → コメント送信に成功している\n case 'postCommentResult': {\n // コメント成功を DPlayer にコールバックで通知\n options.success();\n\n // イベントリスナーを削除\n abort_controller.abort();\n break;\n }\n\n // コメント送信直後に error が送られてきた → コメント送信に失敗している\n case 'error': {\n // コメント失敗を DPlayer にコールバックで通知\n let error = `コメントの送信に失敗しました。(${message.data.code})`;\n switch (message.data.code) {\n case 'COMMENT_POST_NOT_ALLOWED':\n error = 'コメントが許可されていません。';\n break;\n case 'INVALID_MESSAGE':\n error = 'コメント内容が無効です。';\n break;\n }\n console.error(`[LiveCommentManager][WatchSession] Comment sending failed. (Code: ${message.data.code})`);\n options.error(error);\n\n // イベントリスナーを解除\n abort_controller.abort();\n break;\n }\n }\n }, { signal: abort_controller.signal });\n }\n\n\n /**\n * 同じ設定でニコニコ実況に再接続する\n */\n private async reconnect(): Promise {\n console.warn('[LiveCommentManager][WatchSession] Reconnecting...');\n this.player.notice('ニコニコ実況に再接続しています…');\n\n // 前のセッションを破棄\n this.destroy();\n\n // セッションを再初期化\n const result = await this.initSession();\n if (result.is_success === false) {\n console.error('[LiveCommentManager][WatchSession] Reconnection failed.');\n this.player.notice(result.detail);\n }\n }\n\n\n /**\n * 視聴セッションとコメントセッションをそれぞれ閉じる\n */\n public destroy(): void {\n\n // セッションに紐いているすべての EventListener を解除\n // 再接続する場合に備えて AbortController を作り直す\n this.abort_controller.abort();\n this.abort_controller = new AbortController();\n\n // 視聴セッションを閉じる\n if (this.watch_session !== null) {\n this.watch_session.close(); // WebSocket を閉じる\n this.watch_session = null; // null に戻す\n }\n\n // コメントセッションを閉じる\n if (this.comment_session !== null) {\n this.comment_session.close(); // WebSocket を閉じる\n this.comment_session = null; // null に戻す\n }\n\n // 座席保持用のタイマーをクリア\n window.clearInterval(this.keep_seat_interval_id);\n this.keep_seat_interval_id = null;\n this.vpos_base_timestamp = 0;\n\n console.log('[LiveCommentManager][WatchSession] Destroyed.');\n }\n}\n\nexport default LiveCommentManager;\n","\n\nimport DPlayer from 'dplayer';\nimport * as DPlayerType from 'dplayer/dist/d.ts/types/DPlayer';\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport CommentMuteSettings from '@/components/Settings/CommentMuteSettings.vue';\nimport LiveCommentManager, { ICommentData } from '@/services/player/LiveCommentManager';\nimport useUserStore from '@/store/UserStore';\nimport Utils, { CommentUtils } from '@/utils';\n\nexport default Vue.extend({\n name: 'Panel-CommentTab',\n components: {\n CommentMuteSettings,\n },\n data() {\n return {\n\n // ユーティリティをテンプレートで使えるように\n Utils: Utils,\n\n // 手動スクロール状態かどうか\n is_manual_scroll: false,\n\n // 自動スクロール中かどうか\n // 自動スクロール中の場合、scroll イベントが発火しても無視する\n is_auto_scrolling: false,\n\n // コメントリストの配列\n comment_list: [] as ICommentData[],\n\n // コメントリストの要素\n comment_list_element: null as HTMLElement | null,\n\n // コメントリストのドロップダウン関連\n is_comment_list_dropdown_display: false as boolean,\n comment_list_dropdown_top: 0 as number,\n comment_list_dropdown_comment: null as ICommentData | null,\n\n // LiveCommentManager のインスタンス\n live_comment_manager: null as LiveCommentManager | null,\n\n // ニコニコ実況セッションの初期化に失敗した際のエラーメッセージ\n // 視聴中チャンネルのニコニコ実況がないときなどに発生する\n initialize_failed_message: null as string | null,\n\n // visibilitychange イベントのリスナー\n visibilitychange_listener: null as (() => void) | null,\n\n // ResizeObserver のインスタンス\n resize_observer: null as ResizeObserver | null,\n\n // ResizeObserver の監視対象の要素\n resize_observer_element: null as HTMLElement | null,\n\n // コメントのミュート設定のモーダルを表示するか\n comment_mute_settings_modal: false,\n };\n },\n computed: {\n // UserStore に this.userStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useUserStore),\n },\n created() {\n\n // アカウント情報を更新\n this.userStore.fetchUser();\n },\n mounted() {\n\n // コメントリストの要素を取得\n if (this.comment_list_element === null) {\n this.comment_list_element = this.$el.querySelector('.comment-list');\n }\n\n // 現在コメントリストがユーザーイベントでスクロールされているかどうか\n let is_user_scrolling = false;\n\n // mousedown → mouseup 中: スクロールバーをマウスでドラッグ\n // 残念ながらスクロールバーのドラッグ中は mousemove のイベントが発火しないため、直接 is_user_scrolling を設定する\n this.comment_list_element.onmousedown = (event: MouseEvent) => {\n // コメントリストの要素の左上を起点としたカーソルのX座標を求める\n const x = event.clientX - this.comment_list_element.getBoundingClientRect().left;\n // 座標が clientWidth 以上であれば、スクロールバー上で mousedown されたものとする\n if (x > this.comment_list_element.clientWidth) is_user_scrolling = true;\n };\n this.comment_list_element.onmouseup = (event: MouseEvent) => {\n // コメントリストの要素の左上を起点としたカーソルのX座標を求める\n const x = event.clientX - this.comment_list_element.getBoundingClientRect().left;\n // 座標が clientWidth 以上であれば、スクロールバー上で mouseup されたものとする\n if (x > this.comment_list_element.clientWidth) is_user_scrolling = false;\n };\n\n // ユーザーによるスクロールイベントで is_user_scrolling を true にする\n // 0.1 秒後に false にする(継続してイベントが発火すれば再び true になる)\n const on_user_scrolling = () => {\n is_user_scrolling = true;\n window.setTimeout(() => is_user_scrolling = false, 100);\n };\n\n // 現在コメントリストがドラッグされているかどうか\n let is_dragging = false;\n // touchstart → touchend 中: スクロールバーをタップでドラッグ\n this.comment_list_element.ontouchstart = () => is_dragging = true;\n this.comment_list_element.ontouchend = () => is_dragging = false;\n // touchmove + is_dragging 中: コメントリストをタップでドラッグしてスクロール\n this.comment_list_element.ontouchmove = () => is_dragging === true ? on_user_scrolling(): '';\n\n // wheel 中: マウスホイールの回転\n this.comment_list_element.onwheel = on_user_scrolling;\n\n // コメントリストがスクロールされた際、自動スクロール中でない&ユーザーイベントで操作されていれば、手動スクロールモードに設定\n // 手動スクロールモードでは自動スクロールを行わず、ユーザーがコメントリストをスクロールできるようにする\n this.comment_list_element.onscroll = async () => {\n\n // scroll イベントは自動スクロールでも発火してしまうので、ユーザーイベントによるスクロールかを確認しないといけない\n // 自動スクロール中かどうかは is_auto_scrolling が true のときで判定できるはずだが、\n // コメントが多くなると is_auto_scrolling が false なのに scroll イベントが遅れて発火してしまうことがある\n if (this.is_auto_scrolling === false && is_user_scrolling === true) {\n\n // 手動スクロールを有効化\n this.is_manual_scroll = true;\n\n // イベント発火時点では scrollTop の値が完全に下にスクロールされていない場合があるため、0.1秒だけ待つ\n await Utils.sleep(0.1);\n\n // 一番下までスクロールされていたら自動スクロールに戻す\n if ((this.comment_list_element.scrollTop + this.comment_list_element.offsetHeight) >\n (this.comment_list_element.scrollHeight - 10)) { // 一番下から 10px 以内\n this.is_manual_scroll = false; // 手動スクロールを無効化\n }\n }\n };\n },\n // 終了前に実行\n beforeDestroy() {\n\n // ニコニコ実況セッションを破棄\n this.destroy();\n\n // ResizeObserver を終了\n if (this.resize_observer !== null) {\n this.resize_observer.unobserve(this.resize_observer_element);\n }\n },\n methods: {\n\n // ドロップダウンメニューを表示する\n showCommentListDropdown(event: Event, comment: ICommentData) {\n const comment_list_wrapper_rect = (this.$refs.comment_list_wrapper as HTMLDivElement).getBoundingClientRect();\n const comment_list_dropdown_height = 76; // 76px はドロップダウンメニューの高さ\n const comment_button_rect = (event.currentTarget as HTMLElement).getBoundingClientRect();\n // メニューの表示位置をクリックされたコメントに合わせる\n this.comment_list_dropdown_top = comment_button_rect.top - comment_list_wrapper_rect.top;\n // メニューがコメントリストからはみ出るときだけ、表示位置を上側に調整\n if ((this.comment_list_dropdown_top + comment_list_dropdown_height) > comment_list_wrapper_rect.height) {\n this.comment_list_dropdown_top = this.comment_list_dropdown_top - comment_list_dropdown_height + comment_button_rect.height;\n }\n // 表示位置を調整できたので、メニューを表示\n this.comment_list_dropdown_comment = comment;\n this.is_comment_list_dropdown_display = true;\n },\n\n // ドロップダウンメニューを非表示にする\n hideCommentListDropdown() {\n this.is_comment_list_dropdown_display = false;\n this.comment_list = this.comment_list.filter((comment) => {\n return CommentUtils.isMutedComment(comment.text, comment.user_id) === false;\n });\n },\n\n // ミュートするキーワードを追加する\n addMutedKeywords() {\n CommentUtils.addMutedKeywords(this.comment_list_dropdown_comment.text);\n this.hideCommentListDropdown();\n },\n\n // ミュートするニコニコユーザー ID を追加する\n addMutedNiconicoUserIds() {\n CommentUtils.addMutedNiconicoUserIDs(this.comment_list_dropdown_comment.user_id);\n this.hideCommentListDropdown();\n },\n\n // コメントリストを一番下までスクロールする\n async scrollCommentList(smooth: boolean = false) {\n\n // ドロップダウンメニュー表示中なら手動スクロールモードに設定\n if (this.is_comment_list_dropdown_display === true) {\n this.is_manual_scroll = true;\n }\n\n // 手動スクロールモードの時は実行しない\n if (this.is_manual_scroll === true) return;\n\n // 自動スクロール中のフラグを立てる\n this.is_auto_scrolling = true;\n\n // 0.01 秒待って実行し、念押しで2回実行しないと完全に最下部までスクロールされない…(ブラウザの描画バグ?)\n // this.$nextTick() は効かなかった\n for (let index = 0; index < 3; index++) {\n await Utils.sleep(0.01);\n if (smooth === true) { // スムーズスクロール\n this.comment_list_element.scrollTo({top: this.comment_list_element.scrollHeight, left: 0, behavior: 'smooth'});\n } else {\n this.comment_list_element.scrollTo(0, this.comment_list_element.scrollHeight);\n }\n }\n\n // 0.1 秒待つ(重要)\n await Utils.sleep(0.1);\n\n // 自動スクロール中のフラグを降ろす\n this.is_auto_scrolling = false;\n },\n\n // リサイズ時のイベントを初期化\n // プレイヤーが初期化される毎に実行する必要がある\n initReserveObserver() {\n\n // 以前に初期化された ResizeObserver を終了\n if (this.resize_observer !== null) {\n this.resize_observer.unobserve(this.resize_observer_element);\n }\n\n // 監視対象の要素\n this.resize_observer_element = document.querySelector('.watch-player');\n\n // タイムアウト ID\n // 一時的に無効にした transition を有効化する際に利用する\n let animation_timeout_id = null;\n\n // プレイヤーの要素がリサイズされた際に発火するイベント\n const on_resize = () => {\n\n // 映像の要素\n const video_element = document.querySelector('.dplayer-video-wrap-aspect');\n\n // コメント描画領域の要素\n const comment_area_element = document.querySelector('.dplayer-danmaku');\n\n // プレイヤー全体と映像の高さの差(レターボックス)から、コメント描画領域の高さを狭める必要があるかを判定する\n // 2で割っているのは単体の差を測るため\n if (this.resize_observer_element === null || this.resize_observer_element.clientHeight === null) return;\n if (video_element === null || video_element.clientHeight === null) return;\n const letter_box_height = (this.resize_observer_element.clientHeight - video_element.clientHeight) / 2;\n\n const threshold = Utils.isSmartphoneVertical() ? 0 : window.matchMedia('(max-height: 450px)').matches ? 50 : 66;\n if (letter_box_height < threshold) {\n\n // コメント描画領域に必要な上下マージン\n const comment_area_vertical_margin = (threshold - letter_box_height) * 2;\n\n // 狭めるコメント描画領域の幅\n // 映像の要素の幅をそのまま利用する\n const comment_area_width = video_element.clientWidth;\n\n // 狭めるコメント描画領域の高さ\n const comment_area_height = video_element.clientHeight - comment_area_vertical_margin;\n\n // 狭めるコメント描画領域のアスペクト比を求める\n // https://tech.arc-one.jp/asepct-ratio/\n const gcd = (x: number, y: number) => { // 最大公約数を求める関数\n if (y === 0) return x;\n return gcd(y, x % y);\n };\n // 幅と高さの最大公約数を求める\n const gcd_result = gcd(comment_area_width, comment_area_height);\n // 幅と高さをそれぞれ最大公約数で割ってアスペクト比を算出\n const comment_area_height_aspect = `${comment_area_width / gcd_result} / ${comment_area_height / gcd_result}`;\n\n // 一時的に transition を無効化する\n // アスペクト比の設定は連続して行われるが、その際に transition が適用されるとワンテンポ遅れたアニメーションになってしまう\n comment_area_element.style.transition = 'none';\n\n // コメント描画領域に算出したアスペクト比を設定する\n comment_area_element.style.setProperty('--comment-area-aspect-ratio', comment_area_height_aspect);\n\n // コメント描画領域に必要な上下マージンを設定する\n comment_area_element.style.setProperty('--comment-area-vertical-margin', `${comment_area_vertical_margin}px`);\n\n // 以前セットされた setTimeout() を止める\n window.clearTimeout(animation_timeout_id);\n\n // 0.2秒後に実行する\n // 0.2秒より前にもう一度リサイズイベントが来た場合はタイマーがクリアされるため実行されない\n window.setTimeout(() => {\n\n // 再び transition を有効化する\n comment_area_element.style.transition = '';\n\n }, 0.2 * 1000);\n\n } else {\n\n // コメント描画領域に設定したアスペクト比・上下マージンを削除する\n comment_area_element.style.removeProperty('--comment-area-aspect-ratio');\n comment_area_element.style.removeProperty('--comment-area-vertical-margin');\n }\n };\n\n // 要素の監視を開始\n this.resize_observer = new ResizeObserver(on_resize);\n this.resize_observer.observe(this.resize_observer_element);\n\n // 0.6 秒待ってから初回実行\n // チャンネル切り替え後、再初期化されたプレイヤーに適用するため(早いと再初期化前のプレイヤーに適用されてしまう)\n window.setTimeout(on_resize, 0.6 * 1000);\n },\n\n // ニコニコ実況に接続し、セッションを初期化する\n async initSession(player: DPlayer, channel_id: string) {\n\n // リサイズ時のイベントを初期化\n // イベントはプレイヤーの DOM に紐づいているため、プレイヤーが破棄→再初期化される毎に実行する必要がある\n this.initReserveObserver();\n\n // タブが非表示状態のときにコメントを格納する配列\n // タブが表示状態になったらコメントリストにのみ表示する(遅れているのでプレイヤーには表示しない)\n const comment_list_buffer: ICommentData[] = [];\n\n // コメントの最大保持数\n const max_comment_count = 500;\n\n // LiveCommentManager を初期化\n this.live_comment_manager = new LiveCommentManager(\n // DPlayer のインスタンス\n player,\n // チャンネル ID\n channel_id,\n\n // 初回の過去コメント (最大50件) を受信したときのコールバック\n async (initial_comments) => {\n\n // コメントリストに一括で追加\n this.comment_list.push(...initial_comments);\n\n // コメントリストを一番下までスクロール\n this.scrollCommentList();\n },\n\n // コメントを受信したときのコールバック\n // プレイヤーへの描画は LiveCommentManager が行う\n async (comment) => {\n\n // タブが非表示状態のときは、バッファにコメントを追加するだけで終了する\n // ここで追加すると、タブが表示状態になったときに一斉に描画されて大変なことになる\n if (document.visibilityState === 'hidden') {\n comment_list_buffer.push(comment);\n return;\n }\n\n // コメントリストのコメント数が max_comment_count 件を超えたら、古いものから順に削除する\n // 仮想スクロールとはいえ、さすがに max_comment_count 件を超えると重くなりそう\n // 手動スクロール時は実行しない\n if (this.comment_list.length >= max_comment_count && this.is_manual_scroll === false) {\n this.comment_list.splice(0, Math.max(0, this.comment_list.length - max_comment_count));\n }\n\n // コメントリストに追加\n this.comment_list.push(comment);\n\n // コメントリストを一番下までスクロール\n this.scrollCommentList();\n },\n );\n\n // タブが表示状態になったときのイベント\n this.visibilitychange_listener = () => {\n if (document.visibilityState === 'visible') {\n\n // コメントリスト + バッファの合計コメント数が max_comment_count 件を超えたら、\n // コメントリスト内のコメントを古いものから順に削除し、max_comment_count 件になるようにする\n const comment_list_and_buffer_length = this.comment_list.length + comment_list_buffer.length;\n if (comment_list_and_buffer_length >= max_comment_count && this.is_manual_scroll === false) {\n this.comment_list.splice(0, Math.max(0, comment_list_and_buffer_length - max_comment_count));\n }\n\n // バッファ内のコメントをコメントリストに一括で追加する\n this.comment_list.push(...comment_list_buffer);\n comment_list_buffer.length = 0; // バッファを空にする\n\n // コメントリストを一番下までスクロール\n this.scrollCommentList();\n }\n };\n document.addEventListener('visibilitychange', this.visibilitychange_listener);\n\n // ニコニコ実況セッションを初期化する\n const result = await this.live_comment_manager.initSession();\n\n // ニコニコ実況セッションの初期化に失敗した\n // 初期化に失敗した際のエラーメッセージを保存しておく (エラー表示などで利用する)\n // プレイヤーへのエラー表示はすでに LiveCommentManager の方で行われているので、ここでは何もしない\n if (result.is_success === false) {\n this.initialize_failed_message = result.detail;\n }\n },\n\n // コメントを送信する\n sendComment(options: DPlayerType.APIBackendSendOptions) {\n\n // 初期化に失敗しているときは実行せず、保存しておいたエラーメッセージを表示する\n if (this.initialize_failed_message !== null) {\n options.error(this.initialize_failed_message);\n return;\n }\n\n // バリデーション\n if (this.userStore.user === null) {\n options.error('コメントするには、KonomiTV アカウントにログインしてください。');\n return;\n }\n if (this.userStore.user.niconico_user_id === null) {\n options.error('コメントするには、ニコニコアカウントと連携してください。');\n return;\n }\n if (this.userStore.user.niconico_user_premium === false && (options.data.type === 'top' || options.data.type === 'bottom')) {\n options.error('コメントを上下に固定するには、ニコニコアカウントのプレミアム会員登録が必要です。');\n return;\n }\n if (this.userStore.user.niconico_user_premium === false && options.data.size === 'big') {\n options.error('コメントサイズを大きめに設定するには、ニコニコアカウントのプレミアム会員登録が必要です。');\n return;\n }\n\n // ニコニコ実況のコメントサーバーにコメントを送信\n this.live_comment_manager.sendComment(options);\n },\n\n // ニコニコ実況セッションを破棄する\n destroy() {\n\n // タブの表示/非表示の状態が切り替わったときのイベントを削除\n if (this.visibilitychange_listener !== null) {\n document.removeEventListener('visibilitychange', this.visibilitychange_listener);\n this.visibilitychange_listener = null;\n }\n\n // LiveCommentManager を破棄\n if (this.live_comment_manager !== null) {\n this.live_comment_manager.destroy();\n this.live_comment_manager = null;\n }\n\n this.initialize_failed_message = null;\n this.comment_list = [];\n }\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Comment.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Comment.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Comment.vue?vue&type=template&id=6f8c784f&scoped=true&\"\nimport script from \"./Comment.vue?vue&type=script&lang=ts&\"\nexport * from \"./Comment.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Comment.vue?vue&type=style&index=0&id=6f8c784f&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"6f8c784f\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"program-container\"},[_c('section',{staticClass:\"program-broadcaster\"},[_c('img',{staticClass:\"program-broadcaster__icon\",attrs:{\"src\":`${_vm.Utils.api_base_url}/channels/${(_vm.channelsStore.channel_id)}/logo`}}),_c('div',{staticClass:\"program-broadcaster__number\"},[_vm._v(\"Ch: \"+_vm._s(_vm.channelsStore.channel.current.channel_number))]),_c('div',{staticClass:\"program-broadcaster__name\"},[_vm._v(_vm._s(_vm.channelsStore.channel.current.channel_name))])]),_c('section',{staticClass:\"program-info\"},[_c('h1',{staticClass:\"program-info__title\",domProps:{\"innerHTML\":_vm._s(_vm.ProgramUtils.decorateProgramInfo(_vm.channelsStore.channel.current.program_present, 'title'))}}),_c('div',{staticClass:\"program-info__time\"},[_vm._v(\" \"+_vm._s(_vm.ProgramUtils.getProgramTime(_vm.channelsStore.channel.current.program_present))+\" \")]),_c('div',{staticClass:\"program-info__description\",domProps:{\"innerHTML\":_vm._s(_vm.ProgramUtils.decorateProgramInfo(_vm.channelsStore.channel.current.program_present, 'description'))}}),_c('div',{staticClass:\"program-info__genre-container\"},_vm._l((_vm.ProgramUtils.getAttribute(_vm.channelsStore.channel.current.program_present, 'genre', [])),function(genre,genre_index){return _c('div',{key:genre_index,staticClass:\"program-info__genre\"},[_vm._v(\" \"+_vm._s(genre.major)+\" / \"+_vm._s(genre.middle)+\" \")])}),0),_c('div',{staticClass:\"program-info__next\"},[_c('span',{staticClass:\"program-info__next-decorate\"},[_vm._v(\"NEXT\")]),_c('Icon',{staticClass:\"program-info__next-icon\",attrs:{\"icon\":\"fluent:fast-forward-20-filled\",\"width\":\"16px\"}})],1),_c('span',{staticClass:\"program-info__next-title\",domProps:{\"innerHTML\":_vm._s(_vm.ProgramUtils.decorateProgramInfo(_vm.channelsStore.channel.current.program_following, 'title'))}}),_c('div',{staticClass:\"program-info__next-time\"},[_vm._v(\" \"+_vm._s(_vm.ProgramUtils.getProgramTime(_vm.channelsStore.channel.current.program_following))+\" \")]),_c('div',{staticClass:\"program-info__status\"},[_c('div',{staticClass:\"program-info__status-force\",class:`program-info__status-force--${_vm.ChannelUtils.getChannelForceType(_vm.channelsStore.channel.current.channel_force)}`},[_c('Icon',{attrs:{\"icon\":\"fa-solid:fire-alt\",\"height\":\"14px\"}}),_c('span',{staticClass:\"ml-2\"},[_vm._v(\"勢い:\")]),_c('span',{staticClass:\"ml-2\"},[_vm._v(_vm._s(_vm.ProgramUtils.getAttribute(_vm.channelsStore.channel.current, 'channel_force', '--'))+\" コメ/分\")])],1),_c('div',{staticClass:\"program-info__status-viewers ml-5\"},[_c('Icon',{attrs:{\"icon\":\"fa-solid:eye\",\"height\":\"14px\"}}),_c('span',{staticClass:\"ml-2\"},[_vm._v(\"視聴数:\")]),_c('span',{staticClass:\"ml-1\"},[_vm._v(_vm._s(_vm.channelsStore.channel.current.viewers))])],1)])]),_c('section',{staticClass:\"program-detail-container\"},_vm._l((_vm.ProgramUtils.getAttribute(_vm.channelsStore.channel.current.program_present, 'detail', {})),function(detail_text,detail_heading){return _c('div',{key:detail_heading,staticClass:\"program-detail\"},[_c('h2',{staticClass:\"program-detail__heading\"},[_vm._v(_vm._s(detail_heading))]),_c('div',{staticClass:\"program-detail__text\",domProps:{\"innerHTML\":_vm._s(_vm.Utils.URLtoLink(detail_text))}})])}),0)])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport useChannelsStore from '@/store/ChannelsStore';\nimport Utils, { ChannelUtils, ProgramUtils } from '@/utils';\n\nexport default Vue.extend({\n name: 'Panel-ProgramTab',\n data() {\n return {\n // ユーティリティをテンプレートで使えるように\n Utils: Utils,\n ChannelUtils: ChannelUtils,\n ProgramUtils: ProgramUtils,\n };\n },\n computed: {\n // ChannelsStore に this.channelsStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useChannelsStore),\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Program.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Program.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Program.vue?vue&type=template&id=12609bdf&scoped=true&\"\nimport script from \"./Program.vue?vue&type=script&lang=ts&\"\nexport * from \"./Program.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Program.vue?vue&type=style&index=0&id=12609bdf&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"12609bdf\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"twitter-container\"},[_c('v-dialog',{attrs:{\"content-class\":\"zoom-capture-modal-container\",\"max-width\":\"980\",\"transition\":\"slide-y-transition\"},model:{value:(_vm.zoom_capture_modal),callback:function ($$v) {_vm.zoom_capture_modal=$$v},expression:\"zoom_capture_modal\"}},[_c('div',{staticClass:\"zoom-capture-modal\"},[_c('img',{staticClass:\"zoom-capture-modal__image\",attrs:{\"src\":_vm.zoom_capture ? _vm.zoom_capture.image_url: ''}}),_c('a',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"zoom-capture-modal__download\",attrs:{\"href\":_vm.zoom_capture ? _vm.zoom_capture.image_url : '',\"download\":_vm.zoom_capture ? _vm.zoom_capture.filename : ''}},[_c('Icon',{attrs:{\"icon\":\"fa6-solid:download\",\"width\":\"45px\"}})],1)])]),_c('div',{staticClass:\"tab-container\"},[_c('div',{staticClass:\"tab-content tab-content--search\",class:{'tab-content--active': _vm.twitter_active_tab === 'Search'}},[_c('div',{staticClass:\"search px-4\"},[_vm._v(\" リアルタイム検索機能は鋭意開発中です。 \")])]),_c('div',{staticClass:\"tab-content tab-content--timeline\",class:{'tab-content--active': _vm.twitter_active_tab === 'Timeline'}},[_c('div',{staticClass:\"search px-4\"},[_vm._v(\" タイムライン機能は鋭意開発中です。 \")])]),_c('div',{staticClass:\"tab-content tab-content--capture\",class:{'tab-content--active': _vm.twitter_active_tab === 'Capture'}},[_c('div',{staticClass:\"captures\"},_vm._l((_vm.captures),function(capture){return _c('div',{key:capture.image_url,staticClass:\"capture\",class:{\n 'capture--selected': capture.selected,\n 'capture--focused': capture.focused,\n 'capture--disabled': !capture.selected && _vm.tweet_captures.length >= 4,\n },on:{\"click\":function($event){return _vm.clickCapture(capture)}}},[_c('img',{staticClass:\"capture__image\",attrs:{\"src\":capture.image_url}}),_c('div',{staticClass:\"capture__disabled-cover\"}),_c('div',{staticClass:\"capture__selected-number\"},[_vm._v(_vm._s(_vm.tweet_captures.findIndex(blob => blob === capture.blob) + 1))]),_c('Icon',{staticClass:\"capture__selected-checkmark\",attrs:{\"icon\":\"fluent:checkmark-circle-16-filled\"}}),_c('div',{staticClass:\"capture__selected-border\"}),_c('div',{staticClass:\"capture__focused-border\"}),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"capture__zoom\",on:{\"click\":function($event){$event.preventDefault();$event.stopPropagation();_vm.zoom_capture_modal = true; _vm.zoom_capture = capture},\"mousedown\":function($event){$event.preventDefault();$event.stopPropagation();/* 親要素の波紋が広がらないように */}}},[_c('Icon',{attrs:{\"icon\":\"fluent:zoom-in-16-regular\",\"width\":\"32px\"}})],1)],1)}),0),_c('div',{directives:[{name:\"show\",rawName:\"v-show\",value:(_vm.captures.length === 0),expression:\"captures.length === 0\"}],staticClass:\"capture-announce\"},[_c('div',{staticClass:\"capture-announce__heading\"},[_vm._v(\"まだキャプチャがありません。\")]),_vm._m(0)])])]),_c('div',{staticClass:\"tab-button-container\"},[_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"tab-button\",class:{'tab-button--active': _vm.twitter_active_tab === 'Search'},on:{\"click\":function($event){_vm.twitter_active_tab = 'Search'}}},[_c('Icon',{attrs:{\"icon\":\"fluent:search-16-filled\",\"height\":\"18px\"}}),_c('span',{staticClass:\"tab-button__text\"},[_vm._v(\"ツイート検索\")])],1),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"tab-button\",class:{'tab-button--active': _vm.twitter_active_tab === 'Timeline'},on:{\"click\":function($event){_vm.twitter_active_tab = 'Timeline'}}},[_c('Icon',{attrs:{\"icon\":\"fluent:home-16-regular\",\"height\":\"18px\"}}),_c('span',{staticClass:\"tab-button__text\"},[_vm._v(\"タイムライン\")])],1),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"tab-button\",class:{'tab-button--active': _vm.twitter_active_tab === 'Capture'},on:{\"click\":function($event){_vm.twitter_active_tab = 'Capture'}}},[_c('Icon',{attrs:{\"icon\":\"fluent:image-copy-20-regular\",\"height\":\"18px\"}}),_c('span',{staticClass:\"tab-button__text\"},[_vm._v(\"キャプチャ\")])],1)]),_c('div',{staticClass:\"tweet-form\",class:{\n 'tweet-form--focused': _vm.is_tweet_hashtag_form_focused || _vm.is_tweet_text_form_focused,\n 'tweet-form--virtual-keyboard-display': _vm.is_virtual_keyboard_display &&\n (_vm.Utils.hasActiveElementClass('tweet-form__hashtag-form') || _vm.Utils.hasActiveElementClass('tweet-form__textarea')) &&\n (() => {_vm.is_hashtag_list_display = false; return true;})(),\n }},[_c('div',{staticClass:\"tweet-form__hashtag\"},[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.tweet_hashtag),expression:\"tweet_hashtag\"}],staticClass:\"tweet-form__hashtag-form\",attrs:{\"type\":\"search\",\"placeholder\":\"#ハッシュタグ\",\"spellcheck\":\"false\"},domProps:{\"value\":(_vm.tweet_hashtag)},on:{\"input\":[function($event){if($event.target.composing)return;_vm.tweet_hashtag=$event.target.value},function($event){return _vm.updateTweetLetterCount()}],\"focus\":function($event){_vm.is_tweet_hashtag_form_focused = true},\"blur\":function($event){_vm.is_tweet_hashtag_form_focused = false},\"change\":function($event){_vm.tweet_hashtag = _vm.formatHashtag(_vm.tweet_hashtag); _vm.updateTweetLetterCount()}}}),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"tweet-form__hashtag-list-button\",on:{\"click\":function($event){return _vm.clickHashtagListButton()}}},[_c('Icon',{attrs:{\"icon\":\"fluent:clipboard-text-ltr-32-regular\",\"height\":\"22px\"}})],1)]),_c('textarea',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.tweet_text),expression:\"tweet_text\"}],ref:\"tweet_text\",staticClass:\"tweet-form__textarea\",attrs:{\"placeholder\":\"ツイート\",\"spellcheck\":\"false\"},domProps:{\"value\":(_vm.tweet_text)},on:{\"input\":[function($event){if($event.target.composing)return;_vm.tweet_text=$event.target.value},function($event){return _vm.updateTweetLetterCount()}],\"paste\":function($event){return _vm.pasteClipboardData($event)},\"focus\":function($event){_vm.is_tweet_text_form_focused = true},\"blur\":function($event){_vm.is_tweet_text_form_focused = false}}}),_c('div',{staticClass:\"tweet-form__control\"},[_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"account-button\",class:{'account-button--no-login': !_vm.is_logged_in_twitter},on:{\"click\":function($event){return _vm.clickAccountButton()}}},[_c('img',{staticClass:\"account-button__icon\",attrs:{\"src\":_vm.is_logged_in_twitter ? _vm.selected_twitter_account.icon_url : '/assets/images/account-icon-default.png'}}),_c('span',{staticClass:\"account-button__screen-name\"},[_vm._v(\" \"+_vm._s(_vm.is_logged_in_twitter ? `@${_vm.selected_twitter_account.screen_name}` : '連携されていません')+\" \")]),_c('Icon',{staticClass:\"account-button__menu\",attrs:{\"icon\":\"fluent:more-circle-20-regular\",\"width\":\"22px\"}})],1),_c('div',{staticClass:\"limit-meter\"},[_c('div',{staticClass:\"limit-meter__content\",class:{\n 'limit-meter__content--yellow': _vm.tweet_letter_count <= 20,\n 'limit-meter__content--red': _vm.tweet_letter_count <= 0,\n }},[_c('Icon',{staticStyle:{\"margin-right\":\"-2px\"},attrs:{\"icon\":\"fa-brands:twitter\",\"width\":\"12px\"}}),_c('span',[_vm._v(_vm._s(_vm.tweet_letter_count))])],1),_c('div',{staticClass:\"limit-meter__content\"},[_c('Icon',{attrs:{\"icon\":\"fluent:image-16-filled\",\"width\":\"14px\"}}),_c('span',[_vm._v(_vm._s(_vm.tweet_captures.length)+\"/4\")])],1)]),_c('button',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"tweet-button\",attrs:{\"disabled\":!_vm.is_logged_in_twitter || _vm.tweet_letter_count < 0 ||\n (_vm.tweet_letter_count === 140 && _vm.tweet_captures.length === 0)},on:{\"click\":function($event){return _vm.sendTweet()},\"touchstart\":function($event){return _vm.sendTweet()}}},[_c('Icon',{attrs:{\"icon\":\"fa-brands:twitter\",\"height\":\"16px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(\"ツイート\")])],1)])]),_c('div',{staticClass:\"hashtag-list\",class:{\n 'hashtag-list--display': _vm.is_hashtag_list_display,\n 'hashtag-list--virtual-keyboard-display': _vm.is_virtual_keyboard_display && _vm.Utils.hasActiveElementClass('hashtag__input'),\n }},[_c('div',{staticClass:\"hashtag-heading\"},[_c('div',{staticClass:\"hashtag-heading__text\"},[_c('Icon',{attrs:{\"icon\":\"charm:hash\",\"width\":\"17px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(\"ハッシュタグリスト\")])],1),_c('button',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"hashtag-heading__add-button\",on:{\"click\":function($event){_vm.saved_twitter_hashtags.push({id: _vm.Utils.time(), text: '#ここにハッシュタグを入力', editing: false})}}},[_c('Icon',{attrs:{\"icon\":\"fluent:add-12-filled\",\"width\":\"17px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(\"追加\")])],1)]),_c('draggable',{staticClass:\"hashtag-container\",attrs:{\"handle\":\".hashtag__sort-handle\"},model:{value:(_vm.saved_twitter_hashtags),callback:function ($$v) {_vm.saved_twitter_hashtags=$$v},expression:\"saved_twitter_hashtags\"}},_vm._l((_vm.saved_twitter_hashtags),function(hashtag){return _c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\",value:(!hashtag.editing),expression:\"!hashtag.editing\"}],key:hashtag.id,staticClass:\"hashtag\",class:{'hashtag--editing': hashtag.editing},on:{\"click\":function($event){return _vm.clickHashtag(hashtag)}}},[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(hashtag.text),expression:\"hashtag.text\"}],staticClass:\"hashtag__input\",attrs:{\"type\":\"search\",\"spellcheck\":\"false\",\"disabled\":!hashtag.editing},domProps:{\"value\":(hashtag.text)},on:{\"click\":function($event){$event.stopPropagation();},\"input\":function($event){if($event.target.composing)return;_vm.$set(hashtag, \"text\", $event.target.value)}}}),_c('button',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"hashtag__edit-button\",on:{\"click\":function($event){$event.preventDefault();$event.stopPropagation();hashtag.editing = !hashtag.editing;\n hashtag.text = _vm.formatHashtag(hashtag.text, true); _vm.updateTweetLetterCount()}}},[_c('Icon',{attrs:{\"icon\":hashtag.editing ? 'fluent:checkmark-16-filled': 'fluent:edit-16-filled',\"width\":\"17px\"}})],1),_c('button',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"hashtag__delete-button\",on:{\"click\":function($event){$event.preventDefault();$event.stopPropagation();_vm.saved_twitter_hashtags.splice(_vm.saved_twitter_hashtags.indexOf(hashtag), 1)}}},[_c('Icon',{attrs:{\"icon\":\"fluent:delete-16-filled\",\"width\":\"17px\"}})],1),_c('div',{staticClass:\"hashtag__sort-handle\"},[_c('Icon',{attrs:{\"icon\":\"material-symbols:drag-handle-rounded\",\"width\":\"17px\"}})],1)])}),0)],1),_c('div',{staticClass:\"twitter-account-list\",class:{'twitter-account-list--display': _vm.is_twitter_account_list_display}},_vm._l((_vm.userStore.user ? _vm.userStore.user.twitter_accounts : []),function(twitter_account){return _c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],key:twitter_account.id,staticClass:\"twitter-account\",on:{\"click\":function($event){return _vm.updateSelectedTwitterAccount(twitter_account)}}},[_c('img',{staticClass:\"twitter-account__icon\",attrs:{\"src\":twitter_account.icon_url}}),_c('div',{staticClass:\"twitter-account__info\"},[_c('div',{staticClass:\"twitter-account__name\"},[_vm._v(_vm._s(twitter_account.name))]),_c('div',{staticClass:\"twitter-account__screen-name\"},[_vm._v(\"@\"+_vm._s(twitter_account.screen_name))])]),_c('Icon',{directives:[{name:\"show\",rawName:\"v-show\",value:(twitter_account.id === _vm.settingsStore.settings.selected_twitter_account_id),expression:\"twitter_account.id === settingsStore.settings.selected_twitter_account_id\"}],staticClass:\"twitter-account__check\",attrs:{\"icon\":\"fluent:checkmark-16-filled\",\"width\":\"24px\"}})],1)}),0)],1)\n}\nvar staticRenderFns = [function (){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"capture-announce__text\"},[_c('p',{staticClass:\"mt-0 mb-0\"},[_vm._v(\"プレイヤーのキャプチャボタンやショートカットキーでキャプチャを撮ると、ここに表示されます。\")]),_c('p',{staticClass:\"mt-2 mb-0\"},[_vm._v(\"表示されたキャプチャを選択してからツイートすると、キャプチャを付けてツイートできます。\")])])\n}]\n\nexport { render, staticRenderFns }","\n\nimport DPlayer from 'dplayer';\nimport { mapStores } from 'pinia';\nimport Vue, { PropType } from 'vue';\nimport draggable from 'vuedraggable';\n\nimport Twitter from '@/services/Twitter';\nimport { ITwitterAccount } from '@/services/Users';\nimport useChannelsStore from '@/store/ChannelsStore';\nimport useSettingsStore from '@/store/SettingsStore';\nimport useUserStore from '@/store/UserStore';\nimport Utils from '@/utils';\n\n// このコンポーネント内でのキャプチャのインターフェイス\ninterface ITweetCapture {\n blob: Blob;\n filename: string;\n image_url: string;\n selected: boolean;\n focused: boolean;\n}\n\n// このコンポーネント内でのハッシュタグのインターフェイス\ninterface IHashtag {\n id: number;\n text: string;\n editing: boolean;\n}\n\nexport default Vue.extend({\n name: 'Panel-TwitterTab',\n components: {\n draggable,\n },\n props: {\n // プレイヤーのインスタンス\n player: {\n type: null as PropType, // 代入当初は null になるため苦肉の策\n required: true,\n },\n // 仮想キーボードが表示されているかどうか\n is_virtual_keyboard_display: {\n type: Boolean as PropType,\n required: true,\n },\n },\n data() {\n return {\n\n // ユーティリティをテンプレートで使えるように\n Utils: Utils,\n\n // Twitter アカウントを1つでも連携しているかどうか\n is_logged_in_twitter: false,\n\n // 現在ツイート対象として選択されている Twitter アカウント\n selected_twitter_account: null as ITwitterAccount | null,\n\n // 連携している Twitter アカウントリストを表示しているか\n is_twitter_account_list_display: false,\n\n // 保存している Twitter のハッシュタグが入るリスト\n saved_twitter_hashtags: useSettingsStore().settings.saved_twitter_hashtags.map((hashtag, index) => {\n // id プロパティは :key=\"\" に指定するためにつける ID (ミリ秒単位のタイムスタンプ + index で適当に一意になるように)\n return {id: Utils.time() + index, text: hashtag, editing: false} as IHashtag;\n }),\n\n // ハッシュタグリストを表示しているか\n is_hashtag_list_display: false,\n\n // デフォルトで表示される Twitter タブ内のタブ\n twitter_active_tab: useSettingsStore().settings.twitter_active_tab,\n\n // キャプチャを拡大表示するモーダルの表示状態\n zoom_capture_modal: false,\n\n // 現在モーダルで拡大表示中のキャプチャのオブジェクト\n zoom_capture: null as ITweetCapture | null,\n\n // キャプチャリスト\n captures: [] as ITweetCapture[],\n\n // キャプチャリストの要素\n captures_element: null as HTMLDivElement | null,\n\n // ツイートハッシュタグフォームにフォーカスしているか\n is_tweet_hashtag_form_focused: false,\n\n // ツイート本文フォームにフォーカスしているか\n is_tweet_text_form_focused: false,\n\n // ツイートのハッシュタグ\n tweet_hashtag: '',\n\n // ツイート本文\n tweet_text: '',\n\n // ツイートに添付するキャプチャの Blob のリスト\n tweet_captures: [] as Blob[],\n\n // 文字数カウント\n tweet_letter_count: 140,\n\n // ツイートを送信中か (API リクエストを実行するまで)\n is_tweet_sending: false,\n };\n },\n computed: {\n // ChannelsStore / SettingsStore / UserStore に this.channelsStore / this.settingsStore / this.userStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useChannelsStore, useSettingsStore, useUserStore),\n },\n async created() {\n\n // アカウント情報を更新\n await this.userStore.fetchUser();\n\n // ログイン時のみ\n if (this.userStore.is_logged_in === true) {\n\n // 連携している Twitter アカウントがあれば true に設定\n if (this.userStore.user.twitter_accounts.length > 0) {\n this.is_logged_in_twitter = true;\n\n // 現在ツイート対象として選択されている Twitter アカウントの ID が設定されていない or ID に紐づく Twitter アカウントがない\n // 連携している Twitter アカウントのうち、一番最初のものを自動選択する\n // ここで言う Twitter アカウントの ID は DB 上で連番で振られるもので、Twitter アカウントそのものの固有 ID ではない\n if (this.settingsStore.settings.selected_twitter_account_id === null ||\n !this.userStore.user.twitter_accounts.some((twitter_account) => {\n return twitter_account.id === this.settingsStore.settings.selected_twitter_account_id;\n })) {\n this.settingsStore.settings.selected_twitter_account_id = this.userStore.user.twitter_accounts[0].id;\n }\n\n // 現在ツイート対象として選択されている Twitter アカウントを取得・設定\n const twitter_account_index = this.userStore.user.twitter_accounts.findIndex((twitter_account) => {\n // Twitter アカウントの ID が選択されているものと一致する\n return twitter_account.id === this.settingsStore.settings.selected_twitter_account_id;\n });\n this.selected_twitter_account = this.userStore.user.twitter_accounts[twitter_account_index];\n }\n }\n\n // 局タグ追加処理を走らせる (ハッシュタグフォームのフォーマット処理も同時に行われるが、元々空なので無意味)\n this.tweet_hashtag = this.formatHashtag(this.tweet_hashtag);\n this.updateTweetLetterCount();\n },\n beforeDestroy() {\n // 終了前にすべてのキャプチャの Blob URL を revoke してリソースを解放する\n for (const capture of this.captures) {\n URL.revokeObjectURL(capture.image_url);\n }\n },\n watch: {\n\n // 保存しているハッシュタグが変更されたら随時 LocalStorage に保存する\n saved_twitter_hashtags: {\n deep: true,\n handler() {\n this.settingsStore.settings.saved_twitter_hashtags = this.saved_twitter_hashtags.map(hashtag => hashtag.text);\n }\n }\n },\n methods: {\n\n // 文字数カウントを変更するイベント\n updateTweetLetterCount() {\n\n // サロゲートペアを考慮し、スプレッド演算子で一度配列化してから数えている\n // ref: https://qiita.com/suin/items/3da4fb016728c024eaca\n this.tweet_letter_count = 140 - [...this.tweet_hashtag].length - [...this.tweet_text].length;\n },\n\n // クリップボード内のデータがペーストされたときのイベント\n pasteClipboardData(event: ClipboardEvent) {\n\n // 一応配列になっているので回しているが、基本1回のペーストにつき DataTransferItem は1個しか入らない\n for (const clipboard_item of event.clipboardData.items) {\n\n // 画像のみを対象にする (DataTransferItem.type には MIME タイプが入る)\n if (clipboard_item.type.startsWith('image/')) {\n\n // クリップボード内の画像データを File オブジェクトとして取得し、キャプチャリストに追加\n const file = clipboard_item.getAsFile();\n this.addCaptureList(file, file.name);\n }\n }\n },\n\n // ハッシュタグリストボタンが押されたときのイベント\n clickHashtagListButton() {\n this.is_hashtag_list_display = !this.is_hashtag_list_display;\n // すべてのハッシュタグの編集状態を解除する\n for (const hashtag of this.saved_twitter_hashtags) {\n hashtag.editing = false;\n }\n },\n\n // ハッシュタグがクリックされたときのイベント\n clickHashtag(hashtag: IHashtag) {\n this.tweet_hashtag = hashtag.text;\n this.tweet_hashtag = this.formatHashtag(this.tweet_hashtag);\n this.updateTweetLetterCount();\n window.setTimeout(() => this.is_hashtag_list_display = false, 150);\n },\n\n // アカウントボタンが押されたときのイベント\n clickAccountButton() {\n\n // Twitter アカウントが連携されていない場合は Twitter 設定画面に飛ばす\n if (!this.is_logged_in_twitter) {\n\n // 視聴画面以外に遷移するため、フルスクリーンを解除しないと画面が崩れる\n if (document.fullscreenElement) {\n document.exitFullscreen();\n }\n\n this.$router.push({path: '/settings/twitter'});\n return;\n }\n\n // アカウントリストの表示/非表示を切り替え\n this.is_twitter_account_list_display = !this.is_twitter_account_list_display;\n\n // アカウントリストが表示されているなら、ハッシュタグリストを非表示にする\n if (this.is_twitter_account_list_display === true) {\n this.is_hashtag_list_display = false;\n }\n },\n\n // 選択されている Twitter アカウントを更新する\n updateSelectedTwitterAccount(twitter_account: ITwitterAccount) {\n this.settingsStore.settings.selected_twitter_account_id = twitter_account.id;\n this.selected_twitter_account = twitter_account;\n\n // Twitter アカウントリストのオーバーレイを閉じる (少し待ってから閉じたほうが体感が良い)\n window.setTimeout(() => this.is_twitter_account_list_display = false, 150);\n },\n\n // キャプチャリスト内のキャプチャがクリックされたときのイベント\n clickCapture(capture: ITweetCapture) {\n\n // 選択されたキャプチャが3枚まで & まだ選択されていないならキャプチャをツイート対象に追加する\n if (this.tweet_captures.length < 4 && capture.selected === false) {\n capture.selected = true;\n this.tweet_captures.push(capture.blob);\n } else {\n // ツイート対象のキャプチャになっていたら取り除く\n const index = this.tweet_captures.findIndex(blob => blob === capture.blob);\n if (index > -1) {\n this.tweet_captures.splice(index, 1);\n }\n // キャプチャの選択を解除\n capture.selected = false;\n }\n },\n\n // 撮ったキャプチャを親コンポーネントから受け取り、キャプチャリストに追加する\n async addCaptureList(blob: Blob, filename: string) {\n\n if (this.captures_element === null) {\n this.captures_element = this.$el.querySelector('.tab-content--capture');\n }\n\n // 撮ったキャプチャが50件を超えていたら、重くなるので古いものから削除する\n // 削除する前に Blob URL を revoke してリソースを解放するのがポイント\n if (this.captures.length > 50) {\n URL.revokeObjectURL(this.captures[0].image_url);\n this.captures.shift();\n }\n\n // キャプチャリストにキャプチャを追加\n const blob_url = URL.createObjectURL(blob);\n this.captures.push({\n blob: blob,\n filename: filename,\n image_url: blob_url,\n selected: false,\n focused: false,\n });\n\n // キャプチャリストを下にスクロール\n // this.$nextTick() のコールバックで DOM の更新を待つ\n this.$nextTick(() => {\n this.captures_element.scrollTo({\n top: this.captures_element.scrollHeight,\n behavior: 'smooth',\n });\n });\n },\n\n // 撮ったキャプチャに番組タイトルの透かしを描画する\n async drawProgramTitleOnCapture(capture: Blob): Promise {\n\n // キャプチャの Blob を createImageBitmap() で Canvas に描ける ImageBitmap に変換\n const image_bitmap = await createImageBitmap(capture);\n\n // OffscreenCanvas が使えるなら使う (OffscreenCanvas の方がパフォーマンスが良い)\n const canvas = ('OffscreenCanvas' in window) ?\n new OffscreenCanvas(image_bitmap.width, image_bitmap.height) : document.createElement('canvas');\n\n // Canvas にキャプチャを描画\n const context = canvas.getContext('2d', {\n alpha: false,\n desynchronized: true,\n willReadFrequently: false,\n }) as OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;\n context.drawImage(image_bitmap, 0, 0);\n image_bitmap.close();\n\n // 描画設定\n context.font = 'bold 22px \"YakuHanJPs\", \"Open Sans\", \"Hiragino Sans\", \"Noto Sans JP\", sans-serif'; // フォント\n context.fillStyle = 'rgba(255, 255, 255, 70%)'; // 半透明の白\n context.shadowColor = 'rgba(0, 0, 0, 100%)'; // 影の色\n context.shadowBlur = 4; // 影をぼかすしきい値\n context.shadowOffsetX = 0; // 影のX座標\n context.shadowOffsetY = 0; // 影のY座標\n\n // 番組タイトルの透かしを描画\n const title = this.channelsStore.channel.current.program_present?.title ?? '放送休止';\n switch (this.settingsStore.settings.tweet_capture_watermark_position) {\n case 'TopLeft': {\n context.textAlign = 'left'; // 左寄せ\n context.textBaseline = 'top'; // ベースラインを上寄せ\n context.fillText(title, 16, 12);\n break;\n }\n case 'TopRight': {\n context.textAlign = 'right'; // 右寄せ\n context.textBaseline = 'top'; // ベースラインを上寄せ\n context.fillText(title, canvas.width - 16, 12);\n break;\n }\n case 'BottomLeft': {\n context.textAlign = 'left'; // 左寄せ\n context.textBaseline = 'bottom'; // ベースラインを下寄せ\n context.fillText(title, 16, canvas.height - 12);\n break;\n }\n case 'BottomRight': {\n context.textAlign = 'right'; // 右寄せ\n context.textBaseline = 'bottom'; // ベースラインを下寄せ\n context.fillText(title, canvas.width - 16, canvas.height - 12);\n break;\n }\n }\n\n // Blob にして返す\n if (canvas instanceof OffscreenCanvas) {\n return await canvas.convertToBlob({type: 'image/jpeg', quality: 1});\n } else {\n return new Promise(resolve => canvas.toBlob(blob => resolve(blob), 'image/jpeg', 1));\n }\n },\n\n // チャンネル名から対応する局タグを取得する\n // とりあえず三大首都圏 + BS のみ対応\n getChannelHashtag(channel_name: string): string | null {\n // NHK\n if (channel_name.startsWith('NHK総合')) {\n return '#nhk';\n } else if (channel_name.startsWith('NHKEテレ')) {\n return '#etv';\n // 民放\n } else if (channel_name.startsWith('日テレ')) {\n return '#ntv';\n } else if (channel_name.startsWith('読売テレビ')) {\n return '#ytv';\n } else if (channel_name.startsWith('中京テレビ')) {\n return '#chukyotv';\n } else if (channel_name.startsWith('テレビ朝日')) {\n return '#tvasahi';\n } else if (channel_name.startsWith('ABCテレビ')) {\n return '#abc';\n } else if (channel_name.startsWith('メ~テレ')) {\n return '#nagoyatv';\n } else if (channel_name.startsWith('TBS') && !channel_name.includes('TBSチャンネル')) {\n return '#tbs';\n } else if (channel_name.startsWith('MBS')) {\n return '#mbs';\n } else if (channel_name.startsWith('CBC')) {\n return '#cbc';\n } else if (channel_name.startsWith('テレビ東京')) {\n return '#tvtokyo';\n } else if (channel_name.startsWith('テレビ大阪')) {\n return '#tvo';\n } else if (channel_name.startsWith('テレビ愛知')) {\n return '#tva';\n } else if (channel_name.startsWith('フジテレビ')) {\n return '#fujitv';\n } else if (channel_name.startsWith('関西テレビ')) {\n return '#kantele';\n } else if (channel_name.startsWith('東海テレビ')) {\n return '#tokaitv';\n // 独立局\n } else if (channel_name.startsWith('TOKYO MX')) {\n return '#tokyomx';\n } else if (channel_name.startsWith('tvk')) {\n return '#tvk';\n } else if (channel_name.startsWith('チバテレ')) {\n return '#chibatv';\n } else if (channel_name.startsWith('テレ玉')) {\n return '#teletama';\n } else if (channel_name.startsWith('サンテレビ')) {\n return '#suntv';\n } else if (channel_name.startsWith('KBS京都')) {\n return '#kbs';\n // BS・CS\n } else if (channel_name.startsWith('NHKBS1')) {\n return '#nhkbs1';\n } else if (channel_name.startsWith('NHKBSプレミアム')) {\n return '#nhkbsp';\n } else if (channel_name.startsWith('BS日テレ')) {\n return '#bsntv';\n } else if (channel_name.startsWith('BS朝日')) {\n return '#bsasahi';\n } else if (channel_name.startsWith('BS-TBS')) {\n return '#bstbs';\n } else if (channel_name.startsWith('BSテレ東')) {\n return '#bstvtokyo';\n } else if (channel_name.startsWith('BSフジ')) {\n return '#bsfuji';\n } else if (channel_name.startsWith('BS11イレブン')) {\n return '#bs11';\n } else if (channel_name.startsWith('BS12トゥエルビ')) {\n return '#bs12';\n } else if (channel_name.startsWith('AT-X')) {\n return '#at_x';\n }\n\n return null;\n },\n\n // ハッシュタグを整形(余計なスペースなどを削り、全角ハッシュを半角ハッシュへ、全角スペースを半角スペースに置換)\n formatHashtag(tweet_hashtag: string, from_hashtag_list: boolean = false): string {\n\n // ハッシュとスペースの表記ゆれを統一し、連続するハッシュやスペースを1つにする\n const tweet_hashtag_array = tweet_hashtag.trim()\n .replaceAll('♯', '#').replaceAll('#', '#').replace(/#{2,}/g, '#').replaceAll(' ', ' ').replaceAll(/ +/g,' ').split(' ')\n .filter(hashtag => hashtag !== '');\n\n // ハッシュタグがついてない場合にハッシュタグを付与\n for (let index in tweet_hashtag_array) {\n if (!tweet_hashtag_array[index].startsWith('#')) {\n tweet_hashtag_array[index] = `#${tweet_hashtag_array[index]}`;\n }\n }\n\n // 設定でオンになっている場合のみ、視聴中チャンネルの局タグを自動で追加する (ハッシュタグリスト内のハッシュタグは除外)\n if (this.settingsStore.settings.auto_add_watching_channel_hashtag === true && from_hashtag_list === false) {\n const channel_hashtag = this.getChannelHashtag(this.channelsStore.channel.current.channel_name);\n if (channel_hashtag !== null) {\n if (tweet_hashtag_array.includes(channel_hashtag) === false) {\n tweet_hashtag_array.push(channel_hashtag);\n }\n }\n }\n\n return tweet_hashtag_array.join(' ');\n },\n\n // ツイートを送信する\n async sendTweet() {\n\n // 送信中フラグを立てる (重複送信防止)\n if (this.is_tweet_sending === true) {\n return;\n }\n this.is_tweet_sending = true;\n\n // ハッシュタグを整形\n this.tweet_hashtag = this.formatHashtag(this.tweet_hashtag);\n this.updateTweetLetterCount();\n const tweet_hashtag = this.tweet_hashtag;\n\n // 実際に送るツイート本文を作成\n let tweet_text = this.tweet_text;\n if (tweet_hashtag !== '') { // ハッシュタグが入力されているときのみ\n switch (this.settingsStore.settings.tweet_hashtag_position) {\n // ツイート本文の前に追加する\n case 'Prepend': {\n tweet_text = `${tweet_hashtag} ${this.tweet_text}`;\n break;\n }\n // ツイート本文の後に追加する\n case 'Append': {\n tweet_text = `${this.tweet_text} ${tweet_hashtag}`;\n break;\n }\n // ツイート本文の前に追加してから改行する\n case 'PrependWithLineBreak': {\n tweet_text = `${tweet_hashtag}\\n${this.tweet_text}`;\n break;\n }\n // ツイート本文の後に改行してから追加する\n case 'AppendWithLineBreak': {\n tweet_text = `${this.tweet_text}\\n${tweet_hashtag}`;\n break;\n }\n }\n }\n\n // キャプチャへの透かしの描画がオンの場合、キャプチャの Blob を透かし付きのものに差し替える\n const new_tweet_captures: Blob[] = [];\n for (let tweet_capture of this.tweet_captures) {\n if (this.settingsStore.settings.tweet_capture_watermark_position !== 'None') {\n tweet_capture = await this.drawProgramTitleOnCapture(tweet_capture);\n }\n new_tweet_captures.push(tweet_capture);\n }\n\n // ツイート送信 API にリクエスト\n // レスポンスは待たない\n Twitter.sendTweet(this.selected_twitter_account.screen_name, tweet_text, new_tweet_captures).then((result) => {\n this.player.notice(result.message);\n });\n\n // 連投防止のため、フォーム上のツイート本文・キャプチャの選択・キャプチャのフォーカスを消去\n // 送信した感を出す意味合いもある\n for (const capture of this.captures) {\n capture.selected = false;\n capture.focused = false;\n }\n this.tweet_captures = [];\n this.tweet_text = '';\n\n // 送信中フラグを下ろす\n this.is_tweet_sending = false;\n\n // パネルを閉じるように親コンポーネントに伝える\n if (this.settingsStore.settings.fold_panel_after_sending_tweet === true) {\n this.$emit('panel_folding_requested');\n (this.$refs.tweet_text as HTMLTextAreaElement).blur(); // フォーカスを外す\n }\n },\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Twitter.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Twitter.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Twitter.vue?vue&type=template&id=62dbc198&scoped=true&\"\nimport script from \"./Twitter.vue?vue&type=script&lang=ts&\"\nexport * from \"./Twitter.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Twitter.vue?vue&type=style&index=0&id=62dbc198&prod&lang=scss&\"\nimport style1 from \"./Twitter.vue?vue&type=style&index=1&id=62dbc198&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"62dbc198\",\n null\n \n)\n\nexport default component.exports","\nimport Message from '@/message';\nimport APIClient from '@/services/APIClient';\n\n\nclass Captures {\n\n /**\n * キャプチャをサーバーにアップロードし保存する\n * @param blob キャプチャ画像の Blob\n * @param filename サーバーに保存するときのファイル名\n */\n static async uploadCapture(blob: Blob, filename: string): Promise {\n\n // キャプチャ画像の File オブジェクト (= Blob) を FormData に入れる\n // multipart/form-data で送るために必要\n // ref: https://r17n.page/2020/02/04/nodejs-axios-file-upload-api/\n const form_data = new FormData();\n form_data.append('image', blob, filename);\n\n // API リクエストを実行\n const response = await APIClient.post('/captures', form_data, {headers: {'Content-Type': 'multipart/form-data'}});\n\n // エラー処理\n if ('is_error' in response) {\n switch (response.error.message) {\n case 'Permission denied to save the file': {\n Message.error('キャプチャのアップロードに失敗しました。保存先フォルダに書き込み権限がありません。');\n break;\n }\n case 'No space left on the device': {\n Message.error('キャプチャのアップロードに失敗しました。保存先フォルダに空き容量がありません。');\n break;\n }\n case 'Unexpected error occurred while saving the file': {\n Message.error('キャプチャのアップロードに失敗しました。保存中に予期しないエラーが発生しました。');\n break;\n }\n default: {\n APIClient.showGenericError(response, 'キャプチャのアップロードに失敗しました。');\n break;\n }\n }\n return;\n }\n }\n\n // TODO: キャプチャリスト機能の実装時にいろいろ追加する\n}\n\nexport default Captures;\n","\nimport { Buffer } from 'buffer';\n\nimport { convertBlobToPng, copyBlobToClipboard } from 'copy-image-clipboard';\nimport dayjs from 'dayjs';\nimport DPlayer from 'dplayer';\nimport 'dayjs/locale/ja';\nimport * as piexif from 'piexifjs';\n\nimport APIClient from '@/services/APIClient';\nimport Captures from '@/services/Captures';\nimport useChannelsStore from '@/store/ChannelsStore';\nimport useSettingsStore from '@/store/SettingsStore';\nimport Utils from '@/utils';\n\n\n// キャプチャに書き込む EXIF メタデータのインターフェイス\ninterface ICaptureExifData {\n captured_at: string;\n captured_playback_position: number;\n network_id: number;\n service_id: number;\n event_id: number;\n title: string;\n description: string;\n start_time: string;\n end_time: string;\n duration: number;\n caption_text: string | null;\n is_caption_composited: boolean;\n is_comment_composited: boolean;\n}\n\n// CaptureHandler.setEXIFDataToCapture() のオプションのインターフェイス\ninterface ISetEXIFDataToCaptureOptions {\n network_id: number;\n service_id: number;\n event_id: number;\n title: string;\n description: string;\n start_time: string;\n end_time: string;\n duration: number;\n caption_text: string | null;\n is_caption_composited: boolean;\n is_comment_composited: boolean;\n}\n\n// Web フォントを Base64 化したデータ (コメントを SVG の foreignObject としてレンダリングする際に必要)\nlet web_font_noto_sans_base64: string | null = null;\nlet web_font_open_sans_base64: string | null = null;\n\n\nclass CaptureHandler {\n\n private player: DPlayer;\n private player_container: HTMLElement;\n private captured_callback: (blob: Blob, filename: string) => void;\n private capture_button: HTMLDivElement;\n private comment_capture_button: HTMLDivElement;\n private canvas: OffscreenCanvas | HTMLCanvasElement;\n private canvas_context: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;\n private settings_store = useSettingsStore();\n\n constructor(player: DPlayer, captured_callback: (blob: Blob, filename: string) => void) {\n\n this.player = player;\n this.player_container = this.player.container;\n this.captured_callback = captured_callback;\n\n // コメント付きキャプチャボタンの HTML を追加\n // insertAdjacentHTML で .dplayer-icons-right の一番左側に配置する\n // この後に通常のキャプチャボタンが insert されるので、実際は左から2番目\n // TODO: ボタンのデザインをコメント付きだと分かるようなものに変更する\n this.player_container.querySelector('.dplayer-icons.dplayer-icons-right').insertAdjacentHTML('afterbegin', `\n
\n \n \n \n
\n `);\n\n // キャプチャボタンの HTML を追加\n // 標準のスクリーンショット機能は貧弱なので、あえて独自に実装している(そのほうが自由度も高くてやりやすい)\n // insertAdjacentHTML で .dplayer-icons-right の一番左側に配置する\n this.player_container.querySelector('.dplayer-icons.dplayer-icons-right').insertAdjacentHTML('afterbegin', `\n
\n \n \n \n
\n `);\n\n this.comment_capture_button = this.player_container.querySelector('.dplayer-comment-capture-icon');\n this.capture_button = this.player_container.querySelector('.dplayer-capture-icon');\n\n // キャプチャ用の Canvas を初期化\n // パフォーマンス向上のため、一度作成した Canvas は使い回す\n // OffscreenCanvas が使えるなら使う (OffscreenCanvas の方がパフォーマンスが良い)\n this.canvas = ('OffscreenCanvas' in window) ? new OffscreenCanvas(0, 0) : document.createElement('canvas');\n this.canvas_context = this.canvas.getContext('2d', {\n alpha: false,\n desynchronized: true,\n willReadFrequently: false,\n }) as OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;\n\n // 映像の解像度を Canvas サイズとして設定\n // 映像が読み込まれた / 画質が変わった際に Canvas のサイズを映像のサイズに合わせる\n this.canvas.width = 0;\n this.canvas.height = 0;\n player.on('loadedmetadata', async () => {\n this.canvas.width = player.video.videoWidth;\n this.canvas.height = player.video.videoHeight;\n // 映像サイズがちゃんと設定されるまで繰り返す (Safari 対策)\n while (this.canvas.width === 0 && this.canvas.height === 0) {\n await Utils.sleep(0.1);\n this.canvas.width = player.video.videoWidth;\n this.canvas.height = player.video.videoHeight;\n }\n });\n\n // もし Web フォントがダウンロードされていないならダウンロード\n // コメントのレンダリングに最低限必要なウェイトのみダウンロードする\n // 待つ必要はないので非同期で実行\n // ref: https://stackoverflow.com/a/66969479/17124142\n (async () => {\n const web_font_noto_sans_url = 'https://cdn.jsdelivr.net/npm/noto-sans-japanese@1.0.0/fonts/NotoSansJP-Bold.woff2';\n const web_font_open_sans_url = 'https://cdn.jsdelivr.net/npm/open-sans-all@0.1.3/fonts/open-sans-700.woff2';\n const base64_font_prefix = 'data:font/woff2;base64,';\n if (web_font_noto_sans_base64 === null) {\n const web_font_noto_sans: ArrayBuffer = (await APIClient.get(web_font_noto_sans_url, {\n responseType: 'arraybuffer',\n })).data;\n // Buffer で受け取ったデータを Base64 に変換\n web_font_noto_sans_base64 = base64_font_prefix + Buffer.from(web_font_noto_sans).toString('base64');\n }\n if (web_font_open_sans_base64 === null) {\n const web_font_open_sans: ArrayBuffer = (await APIClient.get(web_font_open_sans_url, {\n responseType: 'arraybuffer',\n })).data;\n // Buffer で受け取ったデータを Base64 に変換\n web_font_open_sans_base64 = base64_font_prefix + Buffer.from(web_font_open_sans).toString('base64');\n }\n })();\n }\n\n\n /**\n * 映像をキャプチャして保存する\n * 映像のみと字幕付き (字幕表示時のみ) の両方のキャプチャを生成できる\n * @param with_comments キャプチャにコメントを合成するかどうか\n */\n public async captureAndSave(with_comments: boolean): Promise {\n\n const total_time = Utils.time();\n\n // チャンネル情報を取得 (ライブ視聴画面のみ、ビデオ視聴画面では null になる)\n const channels_store = useChannelsStore();\n const channel = channels_store.is_showing_live ? channels_store.channel.current : null;\n\n // ***** バリデーション *****\n\n // ラジオチャンネルを視聴している場合 (当然映像がないのでキャプチャできない)\n if (channel !== null && channel.is_radiochannel === true) {\n this.player.notice('ラジオチャンネルはキャプチャできません。');\n return;\n }\n\n // まだ映像の表示準備が終わっていない (Canvas の幅/高さが 0 のまま)\n if (this.canvas.width === 0 && this.canvas.height === 0) {\n this.player.notice('読み込み中はキャプチャできません。');\n return;\n }\n\n // コメントが表示されていないのにコメント付きキャプチャしようとした\n if (with_comments === true && this.player.danmaku.showing === false) {\n this.player.notice('コメントを付けてキャプチャするには、コメント表示をオンにしてください。');\n return;\n }\n\n // ***** キャプチャの下準備 *****\n\n // キャプチャ中はキャプチャボタンをハイライトする\n this.addHighlight(with_comments);\n\n // ファイル名(拡張子なし)\n // TODO: ファイル名パターンを変更できるようにする\n const filename_base = `Capture_${dayjs().format('YYYYMMDD-HHmmss')}`;\n const filename = `${filename_base}.jpg`; // 字幕なしキャプチャ\n const filename_caption = `${filename_base}_caption.jpg`; // 字幕ありキャプチャ\n\n // 字幕・文字スーパーの Canvas を取得\n // getRawCanvas() で映像と同じ解像度の Canvas が取得できる\n const caption_canvas: HTMLCanvasElement = this.player.plugins.aribb24Caption.getRawCanvas();\n const superimpose_canvas: HTMLCanvasElement = this.player.plugins.aribb24Superimpose.getRawCanvas();\n\n // 字幕が表示されているか\n // @ts-ignore\n const is_caption_showing = (this.player.plugins.aribb24Caption.isShowing === true &&\n this.player.plugins.aribb24Caption.isPresent());\n\n // 文字スーパーが表示されているか\n // @ts-ignore\n const is_superimpose_showing = (this.player.plugins.aribb24Superimpose.isShowing === true &&\n this.player.plugins.aribb24Superimpose.isPresent());\n\n // 字幕が表示されている場合、表示中の字幕のテキストを取得\n // 取得した字幕のテキストは、キャプチャに字幕が合成されているかに関わらず、常に EXIF メタデータに書き込まれる\n // 字幕が表示されていない場合は null を入れ、キャプチャしたシーンで字幕が表示されていなかったことを明示する\n const caption_text = is_caption_showing ? this.player.plugins.aribb24Caption.getTextContent() : null;\n\n // EXIF に書き込むメタデータを取得する\n // ライブ視聴画面では、番組情報から EXIF に書き込むメタデータを取得する\n let exif_options: ISetEXIFDataToCaptureOptions;\n if (channel !== null) {\n exif_options = {\n network_id: channel.network_id,\n service_id: channel.service_id,\n event_id: channel.program_present?.event_id ?? -1,\n title: channel.program_present?.title ?? '放送休止',\n description: channel.program_present?.description ?? '',\n start_time: channel.program_present?.start_time ?? '2000-01-01T00:00:00+09:00',\n end_time: channel.program_present?.end_time ?? '2000-01-01T00:00:00+09:00',\n duration: channel.program_present?.duration ?? 0,\n caption_text: caption_text,\n is_caption_composited: false, // 後で上書きされる\n is_comment_composited: false, // 後で上書きされる\n };\n // ビデオ視聴画面では、録画番組情報から EXIF に書き込むメタデータを取得する\n } else {\n // TODO\n }\n\n // エクスポートして保存する共通処理\n const export_and_save = async (\n canvas: OffscreenCanvas | HTMLCanvasElement,\n filename: string,\n exif_options: ISetEXIFDataToCaptureOptions,\n ): Promise => {\n\n // Canvas を Blob にエクスポート\n const time = Utils.time();\n let blob: Blob;\n try {\n blob = await this.exportToBlob(canvas);\n } catch (error) {\n console.log(error);\n this.player.notice('キャプチャの保存に失敗しました…');\n return false;\n }\n console.log('[CaptureHandler] Export to Blob:', Utils.mathFloor(Utils.time() - time, 3), 'sec');\n\n // キャプチャに番組情報などのメタデータ (EXIF) をセット\n blob = await this.setEXIFDataToCapture(blob, exif_options);\n\n // キャプチャの保存先: ブラウザでダウンロード or 両方\n if (['Browser', 'Both'].includes(this.settings_store.settings.capture_save_mode)) {\n Utils.downloadBlobData(blob, filename);\n }\n\n // キャプチャの保存先: KonomiTV サーバーにアップロード or 両方\n // 時間がかかるし完了を待つ必要がないので非同期\n if (['UploadServer', 'Both'].includes(this.settings_store.settings.capture_save_mode)) {\n Captures.uploadCapture(blob, filename);\n }\n\n return blob;\n };\n\n // ***** 映像のキャプチャ *****\n\n // null はまだキャプチャしていないことを、false はキャプチャに失敗したことを表す\n let capture_normal: {blob: Blob, filename: string} | null | false = null;\n let capture_caption: {blob: Blob, filename: string} | null | false = null;\n\n // 映像の ImageBitmap を取得\n const image_bitmap = await createImageBitmap(this.player.video);\n\n // もし映像以外に追加で合成するものがないなら、処理の高速化のために ImageBitmap をそのまま Canvas に転送して Blob 化する\n // コメントキャプチャではない & 文字スーパーが表示されていない (=合成処理を行う必要がない) &\n // (字幕が表示されていない or 字幕が表示されているが合成しないように設定されている) 場合\n // コメント付きキャプチャではなく、かつ字幕のない番組では大半がここの処理を通ることになる\n if (with_comments === false && is_superimpose_showing === false &&\n (is_caption_showing === false || this.settings_store.settings.capture_caption_mode === 'VideoOnly')) {\n\n // OffscreenCanvas が使えるなら使う (OffscreenCanvas の方がパフォーマンスが良い)\n const bitmap_canvas = ('OffscreenCanvas' in window) ?\n new OffscreenCanvas(image_bitmap.width, image_bitmap.height) : document.createElement('canvas');\n bitmap_canvas.width = image_bitmap.width;\n bitmap_canvas.height = image_bitmap.height;\n const canvas_context = bitmap_canvas.getContext('bitmaprenderer', {alpha: false}) as ImageBitmapRenderingContext;\n\n // Canvas に映像がキャプチャされた ImageBitmap を転送\n // 描画ではなくゼロコピーで転送しているらしい…?\n canvas_context.transferFromImageBitmap(image_bitmap);\n image_bitmap.close(); // 今後使うことはないので明示的に閉じる\n\n // ファイル名\n // 保存モードが「字幕キャプチャのみ」のとき (=字幕キャプチャのみをキャプチャする設定にしていたが、字幕がそもそもないとき) は、\n // 便宜上字幕ありキャプチャと同じファイル名で保存する\n const filename_real =\n (this.settings_store.settings.capture_caption_mode === 'CompositingCaption') ? filename_caption : filename;\n\n // Blob にエクスポートして保存\n // false が返ってきた場合は失敗を意味する\n const blob = await export_and_save(bitmap_canvas, filename_real, {\n ...exif_options,\n is_caption_composited: false,\n is_comment_composited: false,\n });\n if (blob !== false) {\n capture_normal = {blob: blob, filename: filename_real};\n } else {\n capture_normal = false; // キャプチャのエクスポートに失敗\n }\n\n // キャプチャの Blob をコールバック関数に渡す\n // ここでコールバック関数に渡した Blob が Twitter タブのキャプチャリストに送られる\n if (capture_normal !== false) {\n this.captured_callback(capture_normal.blob, capture_normal.filename);\n }\n\n // ***** 通常実行 (Canvas にキャプチャ以外のデータを重ねて描画する必要があるケース) *****\n } else {\n\n const promises: Promise[] = [];\n\n // Canvas に映像がキャプチャされた ImageBitmap を描画\n this.canvas_context.drawImage(image_bitmap, 0, 0, this.canvas.width, this.canvas.height);\n\n // 文字スーパーを描画 (表示されている場合)\n // 文字スーパー自体が稀だし、文字スーパーなしでキャプチャ撮りたいユースケースはない…はず\n if (is_superimpose_showing === true) {\n this.canvas_context.drawImage(superimpose_canvas, 0, 0, this.canvas.width, this.canvas.height);\n }\n\n // コメント付きキャプチャ: 追加でニコニコ実況のコメントを描画\n let comments_image: HTMLImageElement | null = null;\n if (with_comments === true) {\n comments_image = await this.createCommentsImage();\n await this.drawComments(comments_image);\n }\n\n // ***** 映像のみのキャプチャを保存 *****\n\n // 字幕表示時のキャプチャの保存モード: 映像のみ or 両方\n // 保存モードが「字幕キャプチャのみ」になっているが字幕が表示されていない場合も実行する\n if (['VideoOnly', 'Both'].includes(this.settings_store.settings.capture_caption_mode) || is_caption_showing === false) {\n\n promises.push((async () => {\n\n // ファイル名\n // 保存モードが「字幕キャプチャのみ」のとき (=字幕キャプチャのみをキャプチャする設定にしていたが、字幕がそもそもないとき) は、\n // 便宜上字幕ありキャプチャと同じファイル名で保存する\n const filename_real =\n (this.settings_store.settings.capture_caption_mode === 'CompositingCaption') ? filename_caption : filename;\n\n // Blob にエクスポートして保存\n const blob = await export_and_save(this.canvas, filename_real, {\n ...exif_options,\n is_caption_composited: false,\n is_comment_composited: with_comments,\n });\n if (blob !== false) {\n capture_normal = {blob: blob, filename: filename_real};\n } else {\n capture_normal = false; // キャプチャのエクスポートに失敗\n }\n\n // キャプチャの Blob をコールバック関数に渡す\n // ここでコールバック関数に渡した Blob が Twitter タブのキャプチャリストに送られる\n if (capture_normal !== false) {\n this.captured_callback(capture_normal.blob, capture_normal.filename);\n }\n\n })());\n }\n\n // ***** 字幕付きのキャプチャを保存 *****\n\n // 字幕表示時のキャプチャの保存モード: 字幕キャプチャのみ or 両方\n // 字幕が表示されているときのみ実行(字幕が表示されていないのにやっても意味がない)\n if (['CompositingCaption', 'Both'].includes(this.settings_store.settings.capture_caption_mode) && is_caption_showing === true) {\n\n promises.push((async () => {\n\n // コメント付きキャプチャ: 映像と文字スーパーの描画をやり直す\n // すでに字幕なしキャプチャを生成する過程でコメントを描画してしまっているため、映像描画からやり直す必要がある\n if (with_comments === true) {\n this.canvas_context.drawImage(image_bitmap, 0, 0, this.canvas.width, this.canvas.height);\n if (is_superimpose_showing === true) {\n this.canvas_context.drawImage(superimpose_canvas, 0, 0, this.canvas.width, this.canvas.height);\n }\n }\n image_bitmap.close(); // 今後使うことはないので明示的に閉じる\n\n // 字幕を重ねて描画\n this.canvas_context.drawImage(caption_canvas, 0, 0, this.canvas.width, this.canvas.height);\n\n // コメント付きキャプチャ: 追加でニコニコ実況のコメントを描画\n if (with_comments === true) {\n await this.drawComments(comments_image);\n }\n\n // Blob にエクスポートして保存\n const blob = await export_and_save(this.canvas, filename_caption, {\n ...exif_options,\n is_caption_composited: true,\n is_comment_composited: with_comments,\n });\n if (blob !== false) {\n capture_caption = {blob: blob, filename: filename_caption};\n } else {\n capture_caption = false; // キャプチャのエクスポートに失敗\n }\n\n // キャプチャの Blob をコールバック関数に渡す\n // ここでコールバック関数に渡した Blob が Twitter タブのキャプチャリストに送られる\n if (capture_caption !== false) {\n // 字幕表示時のキャプチャの保存モードが「両方 (Both)」のときのみ、映像のみのキャプチャの生成が終わるまで待ってから実行\n // 必ずキャプチャリストへの追加が [映像のみ] → [字幕付き] の順序で行われるようにする\n if (this.settings_store.settings.capture_caption_mode === 'Both') {\n while (capture_normal === null) {\n // Blob (成功) か false (失敗) が capture_normal に入るまでループ\n await Utils.sleep(0.01);\n }\n }\n this.captured_callback(capture_caption.blob, capture_caption.filename);\n }\n\n })());\n }\n\n // すべてのキャプチャ処理が終わるまで待つ\n await Promise.all(promises);\n }\n\n console.log('[CaptureHandler] Total:', Utils.mathFloor(Utils.time() - total_time, 3), 'sec');\n\n // キャプチャボタンのハイライトを削除する\n this.removeHighlight(with_comments);\n\n // Twitter タブのキャプチャリストに送る処理が最優先なので、コールバックを実行しきった後に時間のかかるクリップボードへのコピーを行う\n for (const capture of [capture_normal, capture_caption]) {\n\n // クリップボードへのコピーが有効なら、キャプチャの Blob をクリップボードにコピー\n // PNG 以外は受け付けないそうなので、JPEG を PNG に変換してからコピーしている\n if (this.settings_store.settings.capture_copy_to_clipboard && capture !== null && capture !== false) {\n try {\n await copyBlobToClipboard(await convertBlobToPng(capture.blob));\n } catch (error) {\n this.player.notice('クリップボードへのキャプチャのコピーに失敗しました…');\n console.error(error);\n }\n }\n }\n }\n\n\n /**\n * キャプチャボタンをハイライトする\n * @param with_comments コメント付きキャプチャボタンをハイライトするか\n */\n private addHighlight(with_comments: boolean = false): void {\n if (with_comments) {\n this.comment_capture_button.classList.add('dplayer-capturing');\n } else {\n this.capture_button.classList.add('dplayer-capturing');\n }\n }\n\n\n /**\n * キャプチャボタンのハイライトを外す\n * @param with_comments コメント付きキャプチャボタンのハイライトを外すか\n */\n private removeHighlight(with_comments: boolean = false): void {\n if (with_comments) {\n this.comment_capture_button.classList.remove('dplayer-capturing');\n } else {\n this.capture_button.classList.remove('dplayer-capturing');\n }\n }\n\n\n /**\n * DPlayer から取得したコメント HTML を SVG 画像の HTMLImageElement に変換する\n * ZenzaWatch のコードを参考にしている\n * ref: https://github.com/segabito/ZenzaWatch/blob/master/packages/lib/src/dom/VideoCaptureUtil.js\n * ref: https://web.archive.org/web/2/https://developer.mozilla.org/ja/docs/Web/HTML/Canvas/Drawing_DOM_objects_into_a_canvas\n * @param html DPlayer から取得したコメント HTML\n * @param width SVG 画像の幅\n * @param height SVG 画像の高さ\n * @returns SVG 画像の HTMLImageElement\n */\n private async commentsHTMLtoSVGImage(html: string, width: number, height: number): Promise {\n\n // SVG の foreignObject を使い、HTML をそのまま SVG に埋め込む\n // SVG なので、CSS はインラインでないと適用されない…\n // DPlayer の danmaku.scss の内容のうち、描画に必要なプロパティのみを列挙 (追加変更したものもある)\n // ref: https://github.com/tsukumijima/DPlayer/blob/master/src/css/danmaku.scss\n const svg = (`\n \n \n
\n \n ${html}\n
\n
\n
\n `).trim();\n\n // Data URL 化して Image オブジェクトにする\n // わざわざ Blob にするよりこっちのほうが楽\n const image = new Image();\n image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n\n // Image は onload を使わなくても await Image.decode() でロードできる\n await image.decode();\n return image;\n }\n\n\n /**\n * DPlayer から表示中のコメントを取得し、SVG 画像の HTMLImageElement を作成する\n * @returns 表示されているコメントが描画された HTMLImageElement\n */\n private async createCommentsImage(): Promise {\n\n // コメントが表示されている要素の HTML を取得する\n let comments_html = this.player.template.danmaku.outerHTML;\n\n // HTML を取得するだけではスクロール中コメントの表示位置が特定できないため、HTML を修正する\n for (const comment of this.player_container.querySelectorAll('.dplayer-danmaku-move')) { // コメントの数だけ置換\n // スクロール中のコメントの表示座標を計算\n const position = comment.getBoundingClientRect().left - this.player.video.getBoundingClientRect().left;\n comments_html = comments_html.replace(/transform: translateX\\(.*?\\);/, `left: ${position}px;`)\n .replaceAll('border: 2px solid #E64F97;', '');\n }\n\n // HTML を画像として取得\n // SVG のサイズはコメントが表示されている要素に合わせる (そうしないとプレイヤー側と一致しない)\n // SVG はベクター画像なので、リサイズしても画質が変わらないはず\n return await this.commentsHTMLtoSVGImage(\n comments_html,\n this.player.template.danmaku.offsetWidth,\n this.player.template.danmaku.offsetHeight,\n );\n }\n\n\n /**\n * 現在表示されているニコニコ実況のコメントを Canvas に描画する\n */\n private async drawComments(comments_image: HTMLImageElement): Promise {\n\n // コメント描画領域がコントロールの表示によりリサイズされている (=16:9でない) 場合も考慮して、コメント要素の offsetWidth から高さを求める\n // 映像の横解像度 (ex: 1920) がコメント描画領域の幅 (ex: 1280) の何倍かの割合 (ex: 1.5 (150%))\n const draw_scale_ratio = this.canvas.width / this.player.template.danmaku.offsetWidth;\n\n // コメント描画領域の高さを映像の横解像度に合わせて(コメント描画領域のアスペクト比を維持したまま)拡大した値\n // 映像の縦解像度が 1080 のとき、コントロールがコメント領域と被っていない or 表示されていないなら、この値は 1080 に近くなる\n const draw_height = this.player.template.danmaku.offsetHeight * draw_scale_ratio;\n\n this.canvas_context.drawImage(comments_image, 0, 0, this.canvas.width, draw_height);\n }\n\n\n /**\n * Canvas もしくは OffscreenCanvas に描画されている画像を Blob に変換する\n * JPEG 画像の品質は 99% にした方が若干 Blob 変換までの速度が速い (?)\n * @param canvas Canvas もしくは OffscreenCanvas\n * @returns Blob 化した画像\n */\n private async exportToBlob(canvas: HTMLCanvasElement | OffscreenCanvas): Promise {\n if ('OffscreenCanvas' in window && canvas instanceof OffscreenCanvas) {\n return await canvas.convertToBlob({type: 'image/jpeg', quality: 0.99});\n } else if (canvas instanceof HTMLCanvasElement) {\n return new Promise((resolve, reject) => {\n canvas.toBlob((blob) => {\n if (blob !== null) {\n resolve(blob);\n } else {\n reject(new Error('Failed to convert canvas to blob'));\n }\n }, 'image/jpeg', 0.99);\n });\n }\n }\n\n\n /**\n * キャプチャ画像に番組情報と撮影時刻、字幕やコメントが合成されているかどうかのメタデータ (EXIF) をセットする\n * @param blob キャプチャ画像の Blob オブジェクト\n * @param options EXIF にセットする番組情報データ・字幕テキスト・字幕が合成されているかどうか・コメントが合成されているかどうか\n * @returns EXIF が追加されたキャプチャ画像の Blob オブジェクト\n */\n private async setEXIFDataToCapture(blob: Blob, options: ISetEXIFDataToCaptureOptions): Promise {\n\n // 番組開始時刻換算のキャプチャ時刻 (秒)\n const captured_playback_position = dayjs().diff(dayjs(options.start_time), 'second', true);\n\n // EXIF の XPComment 領域に入れるメタデータの JSON オブジェクト\n // 撮影時刻とチャンネル・番組を一意に特定できる情報を入れる\n const json: ICaptureExifData = {\n captured_at: dayjs().format('YYYY-MM-DDTHH:mm:ss+09:00'), // ISO8601 フォーマットのキャプチャ時刻\n captured_playback_position: captured_playback_position, // 番組開始時刻換算のキャプチャ時刻 (秒)\n network_id: options.network_id, // 番組が放送されたチャンネルのネットワーク ID\n service_id: options.service_id, // 番組が放送されたチャンネルのサービス ID\n event_id: options.event_id, // 番組のイベント ID\n title: options.title, // 番組タイトル\n description: options.description, // 番組概要\n start_time: options.start_time, // 番組開始時刻 (ISO8601 フォーマット)\n end_time: options.end_time, // 番組終了時刻 (ISO8601 フォーマット)\n duration: options.duration, // 番組長 (秒)\n caption_text: options.caption_text, // 字幕のテキスト (キャプチャした瞬間に字幕が表示されていなかったときは null)\n is_caption_composited: options.is_caption_composited, // 字幕が合成されているか\n is_comment_composited: options.is_comment_composited, // コメントが合成されているか\n };\n\n // 保存する EXIF メタデータを構築\n // ref: 「カメラアプリで体感するWeb App」4.2\n const datetime = dayjs().format('YYYY:MM:DD HH:mm:ss'); // すべてコロンで区切るのがポイント\n const exif: piexif.IExif = {\n '0th': {\n // 必須らしいプロパティ\n // とりあえずデフォルト値 (?) を設定しておく\n [piexif.TagValues.ImageIFD.XResolution]: [72, 1],\n [piexif.TagValues.ImageIFD.YResolution]: [72, 1],\n [piexif.TagValues.ImageIFD.ResolutionUnit]: 2,\n [piexif.TagValues.ImageIFD.YCbCrPositioning]: 1,\n // 撮影時刻\n [piexif.TagValues.ImageIFD.DateTime]: datetime,\n // ソフトウェア名\n [piexif.TagValues.ImageIFD.Software]: `KonomiTV version ${Utils.version}`,\n // Microsoft 拡張のコメント領域(エクスプローラーで出てくるコメント欄と同じもの)\n // ref: https://stackoverflow.com/a/66186660/17124142\n [piexif.TagValues.ImageIFD.XPComment]: [...Buffer.from(JSON.stringify(json), 'ucs2')],\n },\n 'Exif': {\n // 必須らしいプロパティ\n // とりあえずデフォルト値 (?) を設定しておく\n [piexif.TagValues.ExifIFD.ExifVersion]: '0230',\n [piexif.TagValues.ExifIFD.ComponentsConfiguration]: '\\x01\\x02\\x03\\x00',\n [piexif.TagValues.ExifIFD.FlashpixVersion]: '0100',\n [piexif.TagValues.ExifIFD.ColorSpace]: 1,\n // 撮影時刻\n [piexif.TagValues.ExifIFD.DateTimeOriginal]: datetime,\n [piexif.TagValues.ExifIFD.DateTimeDigitized]: datetime,\n },\n };\n const exif_string = piexif.dump(exif); // バイナリ文字列に変換した EXIF データ\n\n // piexifjs はバイナリ文字列か DataURL しか受け付けないので、Blob をバイナリ文字列に変換\n const blob_string: string = await new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(reader.result as string);\n reader.onerror = reject;\n reader.readAsBinaryString(blob); // バイナリ文字列で読み込む\n });\n\n // 画像に EXIF を挿入\n // 戻り値は EXIF が追加された画像のバイナリ文字列 (なぜ未だにバイナリ文字列で実装してるんだ…)\n const blob_string_new = piexif.insert(exif_string, blob_string);\n\n // 画像のバイナリ文字列を ArrayBuffer に変換\n // ref: 「カメラアプリで体感するWeb App」4.2\n const buffer = new Uint8Array(blob_string_new.length);\n for (let index = 0; index < buffer.length; index++) {\n buffer[index] = blob_string_new.charCodeAt(index) & 0xff;\n }\n\n // 新しい Blob を返す\n return new Blob([buffer], {type: blob.type});\n }\n}\n\nexport default CaptureHandler;\n","\n\nimport dayjs from 'dayjs';\nimport DPlayer from 'dplayer';\nimport * as DPlayerType from 'dplayer/dist/d.ts/types/DPlayer';\nimport mpegts from 'mpegts.js';\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport BottomNavigation from '@/components/BottomNavigation.vue';\nimport Channel from '@/components/Panel/Channel.vue';\nimport Comment from '@/components/Panel/Comment.vue';\nimport Program from '@/components/Panel/Program.vue';\nimport Twitter from '@/components/Panel/Twitter.vue';\nimport APIClient from '@/services/APIClient';\nimport { IChannel } from '@/services/Channels';\nimport CaptureHandler from '@/services/player/CaptureHandler';\nimport useChannelsStore from '@/store/ChannelsStore';\nimport useSettingsStore from '@/store/SettingsStore';\nimport Utils, { PlayerUtils, ProgramUtils } from '@/utils';\n\n// 低遅延モードオン時の再生バッファ (秒単位)\n// 0.7 秒程度余裕を持たせる\nconst PLAYBACK_BUFFER_SEC_LOW_LATENCY = 0.7;\n\n// 低遅延モードオフ時の再生バッファ (秒単位)\n// 5秒程度の遅延を許容する\nconst PLAYBACK_BUFFER_SEC = 5.0;\n\nexport default Vue.extend({\n name: 'TV-Watch',\n components: {\n BottomNavigation,\n Channel,\n Comment,\n Program,\n Twitter,\n },\n data() {\n return {\n\n // ユーティリティをテンプレートで使えるように\n Utils: Utils,\n ProgramUtils: ProgramUtils,\n\n // 現在時刻\n time: dayjs().format('YYYY/MM/DD HH:mm:ss'),\n\n // 表示されるパネルのタブ\n tv_panel_active_tab: useSettingsStore().settings.tv_panel_active_tab,\n\n // 背景の URL\n background_url: '',\n\n // プレイヤーのローディング状態\n // 既定でローディングとする\n is_loading: true,\n\n // プレイヤーが映像の再生をバッファリングしているか\n // 視聴開始時以外にも、ネットワークが遅くて再生が一時的に途切れたときなどで表示される\n // 既定でバッファリング中とする\n is_video_buffering: true,\n\n // プレイヤーの背景を表示するか\n // 既定で表示しない\n is_background_display: false,\n\n // コントロールを表示するか\n // 既定で表示する\n is_control_display: true,\n\n // パネルを表示するか\n // panel_display_state が 'AlwaysDisplay' なら常に表示し、'AlwaysFold' なら常に折りたたむ\n // 'RestorePreviousState' なら showed_panel_last_time の値を使い、前回の状態を復元する\n is_panel_display: (() => {\n const settings_store = useSettingsStore();\n switch (settings_store.settings.panel_display_state) {\n case 'AlwaysDisplay':\n return true;\n case 'AlwaysFold':\n return false;\n case 'RestorePreviousState':\n return settings_store.settings.showed_panel_last_time;\n }\n })() as boolean,\n\n // フルスクリーン状態かどうか\n is_fullscreen: false,\n\n // IME 変換中かどうか\n is_ime_composing: false,\n\n // 仮想キーボードが表示されているか\n is_virtual_keyboard_display: false,\n\n // プレイヤーからのコメント送信から間もないかどうか\n is_comment_send_just_did: false,\n\n // インターバル ID\n // ページ遷移時に setInterval(), setTimeout() の実行を止めるのに使う\n // setInterval(), setTimeout() の返り値を登録する\n interval_ids: [] as number[],\n\n // コントロール表示切り替え用のインターバル ID\n // 混ぜるとダメなので独立させる\n control_interval_id: 0,\n\n // ***** チャンネル *****\n\n // ザッピング(「前/次のチャンネル」ボタン or 上下キーショートカット)によるチャンネル移動かどうか\n is_zapping: false,\n\n // ザッピングで連続してチャンネルを切り替えている最中かどうか\n // 「連続して」とは、切り替える間隔が 0.5 秒以下で、再生セッションが初期化される前に次のチャンネルに切り替えたときのこと\n is_zapping_continuously: false,\n\n // ***** プレイヤー *****\n\n // プレイヤー (DPlayer) のインスタンス\n player: null as DPlayer | null,\n\n // プレイヤーの破棄を許可するかどうか\n player_can_be_destroyed: false,\n\n // mpegts.js がサポートされているかどうか\n // mpegts.js がサポートされていない場合は LL-HLS にフォールバックする (基本 iPhone Safari 向け)\n is_mpegts_supported: mpegts.isSupported() === true,\n\n // RomSound の AudioContext\n romsounds_context: null as AudioContext | null,\n\n // RomSound の AudioBuffer(音声データ)が入るリスト\n romsounds_buffers: [] as AudioBuffer[],\n\n // イベントソースのインスタンス\n eventsource: null as EventSource | null,\n\n // フルスクリーン状態が切り替わったときのハンドラー\n fullscreen_handler: null as () => void | null,\n\n // キャプチャハンドラーのインスタンス\n capture_handler: null as CaptureHandler | null,\n\n // ***** キーボードショートカット *****\n\n // ショートカットキーのハンドラー\n shortcut_key_handler: null as (event: KeyboardEvent) => void | null,\n\n // ショートカットキーの最終押下時刻のタイムスタンプ\n shortcut_key_pressed_at: Utils.time(),\n\n // キーボードショートカットの一覧のモーダルを表示するか\n shortcut_key_modal: false,\n\n // キーボードショートカットの一覧に表示するショートカットキーのリスト\n shortcut_key_list: {\n left_column: [\n {\n name: '全般',\n icon: 'fluent:home-20-filled',\n icon_height: '22px',\n shortcuts: [\n { name: '数字キー/テンキーに対応するリモコン番号 (1~12) の地デジチャンネルに切り替える', keys: [{name: '1~9, 0, -(=), ^(~)', icon: false}] },\n { name: '数字キー/テンキーに対応するリモコン番号 (1~12) の BS チャンネルに切り替える', keys: [{name: 'Shift', icon: false}, {name: '1~9, 0, -(=), ^(~)', icon: false}] },\n { name: '前のチャンネルに切り替える', keys: [{name: 'fluent:arrow-up-12-filled', icon: true}] },\n { name: '次のチャンネルに切り替える', keys: [{name: 'fluent:arrow-down-12-filled', icon: true}] },\n { name: 'キーボードショートカットの一覧を表示する', keys: [{name: '/(?)', icon: false}] },\n ]\n },\n {\n name: 'プレイヤー',\n icon: 'fluent:play-20-filled',\n icon_height: '20px',\n shortcuts: [\n { name: '再生 / 一時停止の切り替え', keys: [{name: 'Space', icon: false}] },\n { name: '再生 / 一時停止の切り替え (キャプチャタブ表示時)', keys: [{name: 'Shift', icon: false}, {name: 'Space', icon: false}] },\n { name: 'プレイヤーの音量を上げる', keys: [{name: Utils.CtrlOrCmd(), icon: false}, {name: 'fluent:arrow-up-12-filled', icon: true}] },\n { name: 'プレイヤーの音量を下げる', keys: [{name: Utils.CtrlOrCmd(), icon: false}, {name: 'fluent:arrow-down-12-filled', icon: true}] },\n { name: '停止して0.5秒早戻し', keys: [{name: Utils.CtrlOrCmd(), icon: false}, {name: 'fluent:arrow-left-12-filled', icon: true}] },\n { name: '停止して0.5秒早送り', keys: [{name: Utils.CtrlOrCmd(), icon: false}, {name: 'fluent:arrow-right-12-filled', icon: true}] },\n { name: 'フルスクリーンの切り替え', keys: [{name: 'F', icon: false}] },\n { name: 'ライブストリームの同期', keys: [{name: 'W', icon: false}] },\n { name: 'Picture-in-Picture の表示切り替え', keys: [{name: 'E', icon: false}] },\n { name: '字幕の表示切り替え', keys: [{name: 'S', icon: false}] },\n { name: 'コメントの表示切り替え', keys: [{name: 'D', icon: false}] },\n { name: '映像をキャプチャする', keys: [{name: 'C', icon: false}] },\n { name: '映像をコメントを付けてキャプチャする', keys: [{name: 'V', icon: false}] },\n { name: 'コメント入力フォームにフォーカスする', keys: [{name: 'M', icon: false}] },\n { name: 'コメント入力フォームを閉じる', keys: [{name: Utils.CtrlOrCmd(), icon: false}, {name: 'M', icon: false}] },\n ]\n },\n ],\n right_column: [\n {\n name: 'パネル',\n icon: 'fluent:panel-right-20-filled',\n icon_height: '24px',\n shortcuts: [\n { name: 'パネルの表示切り替え', keys: [{name: 'P', icon: false}] },\n { name: '番組情報タブを表示する', keys: [{name: 'K', icon: false}] },\n { name: 'チャンネルタブを表示する', keys: [{name: 'L', icon: false}] },\n { name: 'コメントタブを表示する', keys: [{name: ';(+)', icon: false}] },\n { name: 'Twitter タブを表示する', keys: [{name: ':(*)', icon: false}] },\n ]\n },\n {\n name: 'Twitter',\n icon: 'fa-brands:twitter',\n icon_height: '22px',\n shortcuts: [\n { name: 'ツイート検索タブを表示する', keys: [{name: '[ (「)', icon: false}] },\n { name: 'タイムラインタブを表示する', keys: [{name: '] (」)', icon: false}] },\n { name: 'キャプチャタブを表示する', keys: [{name: '\(¥)', icon: false}] },\n { name: 'ツイート入力フォームにフォーカスを当てる/フォーカスを外す', keys: [{name: 'Tab', icon: false}] },\n { name: 'キャプチャにフォーカスする', keys: [{name: 'キャプチャタブを表示', icon: false}, {name: 'fluent:arrow-up-12-filled;fluent:arrow-down-12-filled;fluent:arrow-left-12-filled;fluent:arrow-right-12-filled', icon: true}] },\n { name: 'キャプチャを拡大表示する/
キャプチャの拡大表示を閉じる', keys: [{name: 'キャプチャにフォーカス', icon: false}, {name: 'Enter', icon: false}] },\n { name: 'キャプチャを選択する/
キャプチャの選択を解除する', keys: [{name: 'キャプチャにフォーカス', icon: false}, {name: 'Space', icon: false}] },\n { name: 'クリップボード内の画像を
キャプチャとして取り込む', keys: [{name: 'ツイート入力
フォームにフォーカス', icon: false}, {name: Utils.CtrlOrCmd(), icon: false}, {name: 'V', icon: false}] },\n { name: 'ツイートを送信する', keys: [{name: 'Twitter タブを表示', icon: false}, {name: Utils.CtrlOrCmd(), icon: false}, {name: 'Enter', icon: false}] },\n ]\n },\n ]\n }\n };\n },\n computed: {\n // ChannelsStore / SettingsStore に this.channelsStore / this.settingsStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useChannelsStore, useSettingsStore),\n },\n // 開始時に実行\n async created() {\n\n // チャンネル選局のキーボードショートカットを Alt or Option + 数字キー/テンキーに変更する設定が有効なら、\n // キーボードショートカット一覧に反映する\n if (this.settingsStore.settings.tv_channel_selection_requires_alt_key === true) {\n this.shortcut_key_list.left_column[0].shortcuts[0].keys.unshift({name: Utils.AltOrOption(), icon: false});\n this.shortcut_key_list.left_column[0].shortcuts[1].keys.unshift({name: Utils.AltOrOption(), icon: false});\n }\n\n // チャンネル ID をセット\n this.channelsStore.channel_id = this.$route.params.channel_id;\n\n // Virtual Keyboard API に対応している場合は、仮想キーボード周りの操作を自力で行うことをブラウザに伝える\n // この視聴画面のみ\n if ('virtualKeyboard' in navigator) {\n navigator.virtualKeyboard.overlaysContent = true;\n // 仮想キーボードが表示されたり閉じられたときのイベント\n navigator.virtualKeyboard.ongeometrychange = (event) => {\n if (event.target.boundingRect.width === 0 && event.target.boundingRect.height === 0) {\n this.is_virtual_keyboard_display = false;\n } else {\n this.is_virtual_keyboard_display = true;\n }\n };\n }\n\n // 再生セッションを初期化\n this.init();\n\n // RomSound を鳴らすための AudioContext を生成\n this.romsounds_context = new AudioContext();\n\n // 01 ~ 14 まですべての RomSound を読み込む\n for (let index = 1; index <= 14; index++) {\n\n // ArrayBuffer として RomSound を取得\n const url = `/assets/romsounds/${index.toString().padStart(2, '0')}.wav`;\n const audio_data = await APIClient.get(url, {\n baseURL: '', // BaseURL を明示的にクライアントのルートに設定\n responseType: 'arraybuffer',\n });\n\n // ArrayBuffer をデコードして AudioBuffer にし、すぐ呼び出せるように貯めておく\n // ref: https://ics.media/entry/200427/\n this.romsounds_buffers.push(await this.romsounds_context.decodeAudioData(audio_data.data));\n }\n },\n // 終了前に実行\n beforeDestroy() {\n\n // 仮想キーボード周りの操作をブラウザに戻す\n if ('virtualKeyboard' in navigator) {\n navigator.virtualKeyboard.overlaysContent = false;\n }\n\n // destroy() を実行\n // 別のページへ遷移するため、DPlayer のインスタンスを確実に破棄する\n // さもなければ、ブラウザがリロードされるまでバックグラウンドで永遠に再生され続けてしまう\n // 不正な ID のため 404 ページに遷移されるときは実行しない\n if (this.channelsStore.channel.current.channel_id !== 'gr999') {\n this.destroy(true);\n }\n\n // AudioContext のリソースを解放\n if (this.romsounds_context !== null) {\n this.romsounds_context.close();\n }\n\n // このページから離れるので、チャンネル ID を gr000 (ダミー値) に戻す\n this.channelsStore.channel_id = 'gr000';\n },\n // チャンネル切り替え時に実行\n // コンポーネント(インスタンス)は再利用される\n // ref: https://v3.router.vuejs.org/ja/guide/advanced/navigation-guards.html#%E3%83%AB%E3%83%BC%E3%83%88%E5%8D%98%E4%BD%8D%E3%82%AB%E3%82%99%E3%83%BC%E3%83%88%E3%82%99\n beforeRouteUpdate(to, from, next) {\n\n // 前の再生セッションを破棄して終了する\n const destroy_promise = this.destroy(false, this.is_zapping_continuously);\n\n // 連続してチャンネルを切り替えていることを示すフラグを立てる\n // このフラグは再生セッションが初期化されるタイミングで必ず降ろされる\n this.is_zapping_continuously = true;\n\n // チャンネル ID を次のチャンネルのものに切り替える\n this.channelsStore.channel_id = to.params.channel_id;\n\n // ハッシュタグフォームのリセットがオンなら、ハッシュタグフォームを空にする\n if (this.settingsStore.settings.reset_hashtag_when_program_switches === true) {\n (this.$refs.Twitter as InstanceType).tweet_hashtag = '';\n }\n\n (async () => {\n\n // ザッピング(「前/次のチャンネル」ボタン or 上下キーショートカット)によるチャンネル移動時のみ、\n // 0.5秒だけ待ってから新しい再生セッションを初期化する\n // 連続してチャンネルを切り替えた際に毎回再生処理を開始しないように猶予を設ける\n if (this.is_zapping === true) {\n this.is_zapping = false;\n this.interval_ids.push(window.setTimeout(() => {\n this.is_zapping_continuously = false; // 新しいセッションを初期化するので、フラグを下ろす\n destroy_promise.then(() => this.init()); // destroy() の実行完了を待ってから初期化する\n }, 0.5 * 1000));\n\n // 通常のチャンネル移動時は、すぐに再生セッションを初期化する\n } else {\n this.is_zapping_continuously = false; // 新しいセッションを初期化するので、フラグを下ろす\n destroy_promise.then(() => this.init()); // destroy() の実行完了を待ってから初期化する\n }\n })();\n\n // 次のルートに置き換え\n next();\n },\n watch: {\n\n // 視聴中のチャンネルが変更されたときのイベント\n 'channelsStore.channel': {\n immediate: true,\n handler(\n new_channel: {previous: IChannel; current: IChannel; next: IChannel;},\n old_channel: {previous: IChannel; current: IChannel; next: IChannel;} | undefined,\n ) {\n\n // old_channel が undefined の場合は、初回のイベント発火なので何もしない\n if (old_channel === undefined) {\n return;\n }\n\n // Twitter コンポーネントのインスタンスを取得\n const twitter_component = this.$refs.Twitter as InstanceType;\n\n // 前のチャンネル情報と次のチャンネル情報で channel_id が変わってたら局タグ追加処理を走らせる\n if (new_channel.current.channel_id !== old_channel.current.channel_id) {\n const old_channel_hashtag = twitter_component.getChannelHashtag(old_channel.current.channel_name) ?? '';\n twitter_component.tweet_hashtag =\n twitter_component.formatHashtag(twitter_component.tweet_hashtag.replaceAll(old_channel_hashtag, ''));\n twitter_component.updateTweetLetterCount();\n }\n\n // 取得したチャンネル情報と現在のチャンネル情報の NID-SID-EID の組み合わせが異なる場合\n if ((new_channel.current.id !== old_channel.current.id) || // チャンネルが異なる\n (new_channel.current.program_present !== null && old_channel.current.program_present === null) || // 番組情報あり→番組情報なし\n (new_channel.current.program_present === null && old_channel.current.program_present !== null) || // 番組情報なし→番組情報あり\n ((new_channel.current.program_present !== null && old_channel.current.program_present !== null) &&\n (new_channel.current.program_present.id !== old_channel.current.program_present.id))) { // 番組情報あり→番組情報あり & 番組が異なる\n\n // ハッシュタグフォームのリセットがオンなら、ハッシュタグフォームを空にする\n if (this.settingsStore.settings.reset_hashtag_when_program_switches === true) {\n twitter_component.tweet_hashtag = twitter_component.formatHashtag('');\n twitter_component.updateTweetLetterCount();\n }\n }\n },\n },\n\n // 前回視聴画面を開いた際にパネルが表示されていたかどうかを保存\n is_panel_display() {\n this.settingsStore.settings.showed_panel_last_time = this.is_panel_display;\n }\n },\n methods: {\n\n // 再生セッションを初期化する\n async init() {\n\n // ローディング中の背景画像をランダムで設定\n this.background_url = PlayerUtils.generatePlayerBackgroundURL();\n\n // コントロール表示タイマーを実行\n this.controlDisplayTimer();\n\n // 現在時刻を1秒おきに更新\n this.interval_ids.push(window.setInterval(() => this.time = dayjs().format('YYYY/MM/DD HH:mm:ss'), 1 * 1000));\n\n // 00秒までの残り秒数を取得\n // 現在 16:01:34 なら 26 (秒) になる\n const residue_second = 60 - new Date().getSeconds();\n\n // 00秒になるまで待ってから実行するタイマー\n // 番組は基本1分単位で組まれているため、20秒や45秒など中途半端な秒数で更新してしまうと番組情報の反映が遅れてしまう\n this.interval_ids.push(window.setTimeout(() => {\n\n // この時点で00秒なので、チャンネル情報を更新\n this.channelsStore.update(true);\n this.update();\n\n // 以降、30秒おきにチャンネル情報を更新\n this.interval_ids.push(window.setInterval(() => {\n this.channelsStore.update(true);\n this.update();\n }, 30 * 1000));\n\n }, residue_second * 1000));\n\n // チャンネル情報を更新 (初回)\n await this.channelsStore.update();\n this.update();\n },\n\n // 画面を更新する\n async update() {\n\n // チャンネル ID が未定義なら実行しない(フェイルセーフ)\n if (this.$route.params.channel_id === undefined) {\n return;\n }\n\n // もし現時点でチャンネル ID が gr999 だった場合、チャンネル情報に存在しない不正な ID なので、404 ページにリダイレクト\n if (this.channelsStore.channel.current.channel_id === 'gr999') {\n this.$router.push({path: '/not-found/'});\n return;\n }\n\n // プレイヤーがまだ初期化されていない or 他のチャンネルからの切り替えですでにプレイヤーが初期化されているけど破棄が可能\n // update() 自体は初期化時以外にも1分ごとに定期実行されるため、その際に毎回プレイヤーを再初期化しないようにする\n if (this.player === null || this.player_can_be_destroyed === true) {\n\n // プレイヤー (DPlayer) 周りのセットアップ\n this.initPlayer();\n\n // サーバーから送られてくるメッセージのイベントハンドラーを初期化\n this.initEventHandler();\n\n // キャプチャのイベントハンドラーを初期化\n this.initCaptureHandler();\n\n // ショートカットキーのイベントハンドラーを初期化\n // 事前に前のイベントハンドラーを削除しておかないと、重複してキー操作が実行されてしまう\n // 直前で実行しないと上下キーでのチャンネル操作が動かなくなる\n document.removeEventListener('keydown', this.shortcut_key_handler);\n this.initShortcutKeyHandler();\n }\n\n if (this.player === null) {\n return; // 復旧不可能 (発生しないはずだが、書いとかないと TypeScript に怒られる)\n }\n\n // 副音声がない番組でプレイヤー上で副音声に切り替えられないように\n // 音声多重放送でもデュアルモノでもない番組のみ\n if ((this.channelsStore.channel.current.program_present === null) ||\n ((this.channelsStore.channel.current.program_present.primary_audio_type !== '1/0+1/0モード(デュアルモノ)') &&\n (this.channelsStore.channel.current.program_present.secondary_audio_type === null))) {\n\n // クラスを付与\n this.player.template.audioItem[1].classList.add('dplayer-setting-audio-item--disabled');\n\n // 現在副音声が選択されている可能性を考慮し、明示的に主音声に切り替える\n if (this.player.plugins.mpegts !== undefined || this.player.plugins.liveLLHLSForKonomiTV !== undefined) {\n // プレイヤーの初期化が完了するまで少し待つ\n while (this.player === null) {\n await Utils.sleep(0.1);\n }\n this.player.template.audioItem[0].classList.add('dplayer-setting-audio-current');\n this.player.template.audioItem[1].classList.remove('dplayer-setting-audio-current');\n this.player.template.audioValue.textContent = this.player.tran('Primary audio');\n try {\n if (this.player.plugins.mpegts !== undefined && this.player.plugins.mpegts instanceof mpegts.MSEPlayer) {\n this.player.plugins.mpegts.switchPrimaryAudio();\n }\n if (this.player.plugins.liveLLHLSForKonomiTV !== undefined) {\n this.player.plugins.liveLLHLSForKonomiTV.switchPrimaryAudio();\n }\n } catch (error) {\n // pass\n }\n }\n\n // 音声多重放送かデュアルモノなので、副音声への切り替えを有効化\n } else {\n this.player.template.audioItem[1].classList.remove('dplayer-setting-audio-item--disabled');\n }\n\n // MediaSession API を使い、メディア通知の表示をカスタマイズ\n if ('mediaSession' in navigator) {\n\n // アートワークとして表示するアイコン\n const artwork = [\n {src: '/assets/images/icons/icon-maskable-192px.png', sizes: '192x192', type: 'image/png'},\n {src: '/assets/images/icons/icon-maskable-512px.png', sizes: '512x512', type: 'image/png'},\n ];\n\n // メディア通知の表示をカスタマイズ\n navigator.mediaSession.metadata = new MediaMetadata({\n title: this.channelsStore.channel.current.program_present?.title ?? '放送休止',\n artist: this.channelsStore.channel.current.channel_name,\n artwork: artwork,\n });\n\n // 再生状況のステータスを設定\n if ('setPositionState' in navigator.mediaSession) {\n navigator.mediaSession.setPositionState({\n duration: 0, // ライブなので0(長さなしを表すらしい)に設定\n playbackRate: 1, // ライブなので再生速度は常に1になる\n });\n }\n\n // 一旦既存のイベントハンドラーを削除\n navigator.mediaSession.setActionHandler('play', null);\n navigator.mediaSession.setActionHandler('pause', null);\n navigator.mediaSession.setActionHandler('previoustrack', null);\n navigator.mediaSession.setActionHandler('nexttrack', null);\n\n // メディア通知上のボタンが押されたときのイベント\n navigator.mediaSession.setActionHandler('play', () => this.player?.play()); // 再生\n navigator.mediaSession.setActionHandler('pause', () => this.player?.pause()); // 停止\n navigator.mediaSession.setActionHandler('previoustrack', async () => { // 前のチャンネルに切り替え\n navigator.mediaSession.metadata = new MediaMetadata({\n title: this.channelsStore.channel.previous.program_present?.title ?? '放送休止',\n artist: this.channelsStore.channel.previous.channel_name,\n artwork: artwork,\n });\n // ルーティングを前のチャンネルに置き換える\n await this.$router.push({path: `/tv/watch/${this.channelsStore.channel.previous.channel_id}`});\n });\n navigator.mediaSession.setActionHandler('nexttrack', async () => { // 次のチャンネルに切り替え\n navigator.mediaSession.metadata = new MediaMetadata({\n title: this.channelsStore.channel.next.program_present?.title ?? '放送休止',\n artist: this.channelsStore.channel.next.channel_name,\n artwork: artwork,\n });\n // ルーティングを次のチャンネルに置き換える\n await this.$router.push({path: `/tv/watch/${this.channelsStore.channel.next.channel_id}`});\n });\n }\n },\n\n // マウスが動いたりタップされた時のイベント\n // 3秒間何も操作がなければコントロールを非表示にする\n controlDisplayTimer(event: Event | null = null, is_player_event: boolean = false) {\n\n // タッチデバイスかどうか\n // DPlayer の UA 判定コードと同一\n const is_touch_device = /iPhone|iPad|iPod|Windows|Macintosh|Android|Mobile/i.test(navigator.userAgent) && 'ontouchend' in document;\n\n // タッチデバイスで mousemove 、あるいはタッチデバイス以外で touchmove か click が発火した時は実行じない\n if (is_touch_device == true && event !== null && event.type === 'mousemove') return;\n if (is_touch_device == false && event !== null && (event.type === 'touchmove' || event.type === 'click')) return;\n\n // 以前セットされたタイマーを止める\n window.clearTimeout(this.control_interval_id);\n\n // setTimeout に渡すタイマー関数\n const timeout = () => {\n\n // コメント入力フォームが表示されているときは実行しない\n // タイマーを掛け直してから抜ける\n if (this.player !== null && this.player.template.controller.classList.contains('dplayer-controller-comment')) {\n this.control_interval_id = window.setTimeout(timeout, 3 * 1000);\n return;\n }\n\n // コントロールを非表示にする\n this.is_control_display = false;\n\n // プレイヤーのコントロールと設定パネルを非表示にする\n if (this.player !== null) {\n this.player.controller.hide();\n this.player.setting.hide();\n }\n };\n\n // タッチデバイスでプレイヤー画面がクリックされたとき\n if (is_touch_device === true && is_player_event === true) {\n\n // プレイヤーのコントロールの表示状態に合わせる\n if (this.player?.controller.isShow()) {\n\n // コントロールを表示する\n this.is_control_display = true;\n\n // プレイヤーのコントロールを表示する\n this.player.controller.show();\n\n // 3秒間何も操作がなければコントロールを非表示にする\n // 3秒間の間一度でもマウスが動けばタイマーが解除されてやり直しになる\n this.control_interval_id = window.setTimeout(timeout, 3 * 1000);\n\n } else {\n\n // コントロールを非表示にする\n this.is_control_display = false;\n\n // プレイヤーのコントロールと設定パネルを非表示にする\n this.player?.controller.hide();\n this.player?.setting.hide();\n }\n\n // それ以外の画面がクリックされたとき\n } else {\n\n // コントロールを表示する\n this.is_control_display = true;\n\n // プレイヤーのコントロールを表示する\n if (this.player !== null) {\n this.player.controller.show();\n }\n\n // 3秒間何も操作がなければコントロールを非表示にする\n // 3秒間の間一度でもマウスが動けばタイマーが解除されてやり直しになる\n this.control_interval_id = window.setTimeout(timeout, 3 * 1000);\n }\n },\n\n // プレイヤーを初期化する\n initPlayer() {\n\n // mpegts.js を window 直下に入れる\n // こうしないと DPlayer が mpegts.js を認識できない\n (window as any).mpegts = mpegts;\n\n // すでに DPlayer が初期化されている場合は破棄する\n // チャンネル切り替え時などが該当する\n if (this.player !== null && this.player_can_be_destroyed === true) {\n try {\n this.player.destroy();\n } catch (error) {\n // mpegts.js をうまく破棄できない場合\n if (this.player.plugins.mpegts !== undefined) {\n this.player.plugins.mpegts.destroy();\n }\n }\n this.player_can_be_destroyed = false;\n this.player = null;\n }\n\n // 低遅延モードであれば低遅延向けの再生バッファを、そうでなければ通常の再生バッファをセット (秒単位)\n const playback_buffer_sec = this.settingsStore.settings.tv_low_latency_mode ?\n PLAYBACK_BUFFER_SEC_LOW_LATENCY : PLAYBACK_BUFFER_SEC;\n\n // DPlayer を初期化\n this.player = new DPlayer({\n container: this.$el.querySelector('.watch-player__dplayer')!,\n theme: '#E64F97', // テーマカラー\n lang: 'ja-jp', // 言語\n live: true, // ライブモード\n liveSyncMinBufferSize: this.is_mpegts_supported ? playback_buffer_sec - 0.1 : 0, // ライブモードで同期する際の最小バッファサイズ\n loop: false, // ループ再生 (ライブのため無効化)\n airplay: false, // AirPlay 機能 (うまく動かないため無効化)\n autoplay: true, // 自動再生\n hotkey: false, // ショートカットキー(こちらで制御するため無効化)\n screenshot: false, // スクリーンショット (こちらで制御するため無効化)\n volume: 1.0, // 音量の初期値\n // 映像\n video: {\n // デフォルトの品質\n // ラジオチャンネルでは常に 48KHz/192kbps に固定する\n defaultQuality: (this.channelsStore.channel.current.is_radiochannel) ?\n '48kHz/192kbps' : this.settingsStore.settings.tv_streaming_quality,\n // 品質リスト\n quality: (() => {\n const qualities: DPlayerType.VideoQuality[] = [];\n\n // ラジオチャンネル\n // API が受け付ける品質の値は通常のチャンネルと同じだが (手抜き…)、実際の品質は 48KHz/192kbps で固定される\n // ラジオチャンネルの場合は、1080p と渡しても 48kHz/192kbps 固定の音声だけの MPEG-TS が配信される\n if (this.channelsStore.channel.current.is_radiochannel) {\n // mpegts.js\n if (this.is_mpegts_supported === true) {\n qualities.push({\n name: '48kHz/192kbps',\n type: 'mpegts',\n url: `${Utils.api_base_url}/streams/live/${this.channelsStore.channel_id}/1080p/mpegts`,\n });\n // LL-HLS (mpegts.js がサポートされていない場合)\n } else {\n qualities.push({\n name: '48kHz/192kbps',\n type: 'live-llhls-for-KonomiTV',\n url: `${Utils.api_base_url}/streams/live/${this.channelsStore.channel_id}/1080p/ll-hls`,\n });\n }\n\n // 通常のチャンネル\n } else {\n\n // ブラウザが H.265 / HEVC の再生に対応していて、かつ通信節約モードが有効なとき\n // API に渡す画質に -hevc のプレフィックスをつける\n let hevc_prefix = '';\n if (PlayerUtils.isHEVCVideoSupported() && this.settingsStore.settings.tv_data_saver_mode === true) {\n hevc_prefix = '-hevc';\n }\n\n // 品質リストを作成\n for (const quality of ['1080p-60fps', '1080p', '810p', '720p', '540p', '480p', '360p', '240p']) {\n // mpegts.js\n if (this.is_mpegts_supported === true) {\n qualities.push({\n name: quality === '1080p-60fps' ? '1080p (60fps)' : quality,\n type: 'mpegts',\n url: `${Utils.api_base_url}/streams/live/${this.channelsStore.channel_id}/${quality}${hevc_prefix}/mpegts`,\n });\n // LL-HLS (mpegts.js がサポートされていない場合)\n } else {\n qualities.push({\n name: quality === '1080p-60fps' ? '1080p (60fps)' : quality,\n type: 'live-llhls-for-KonomiTV',\n url: `${Utils.api_base_url}/streams/live/${this.channelsStore.channel_id}/${quality}${hevc_prefix}/ll-hls`,\n });\n }\n }\n }\n return qualities;\n })(),\n },\n // コメント\n danmaku: {\n user: 'KonomiTV', // 便宜上 KonomiTV に固定\n speedRate: this.settingsStore.settings.comment_speed_rate, // コメントの流れる速度\n fontSize: this.settingsStore.settings.comment_font_size, // コメントのフォントサイズ\n },\n // コメント API バックエンド\n apiBackend: {\n // コメント取得時\n read: (options) => {\n // 空の配列を返す (こうするとコメント0件と認識される)\n options.success([]);\n },\n // コメント送信時\n send: async (options) => {\n // Comment コンポーネント内のコメント送信メソッドを呼び出す\n // ref: https://stackoverflow.com/a/65729556/17124142 ($refs への型設定)\n (this.$refs.Comment as InstanceType).sendComment(options);\n },\n },\n // プラグイン\n pluginOptions: {\n // mpegts.js\n mpegts: {\n config: {\n // Web Worker を有効にする\n enableWorker: true,\n // IO 層のバッファを禁止する\n enableStashBuffer: false,\n // HTMLMediaElement の内部バッファによるライブストリームの遅延を追跡する\n // liveBufferLatencyChasing と異なり、いきなり再生時間をスキップするのではなく、\n // 再生速度を少しだけ上げることで再生を途切れさせることなく遅延を追跡する\n liveSync: this.settingsStore.settings.tv_low_latency_mode,\n // 許容する HTMLMediaElement の内部バッファの最大値 (秒単位, 3秒)\n liveSyncMaxLatency: 3,\n // HTMLMediaElement の内部バッファ (遅延) が liveSyncMaxLatency を超えたとき、ターゲットとする遅延時間 (秒単位)\n liveSyncTargetLatency: playback_buffer_sec,\n // ライブストリームの遅延の追跡に利用する再生速度 (x1.1)\n // 遅延が 3 秒を超えたとき、遅延が playback_buffer_sec を下回るまで再生速度が x1.1 に設定される\n liveSyncPlaybackRate: 1.1,\n }\n },\n // aribb24.js\n aribb24: {\n // 描画フォント\n normalFont: `\"${this.settingsStore.settings.caption_font}\", \"Rounded M+ 1m for ARIB\", sans-serif`,\n // 縁取りする色\n forceStrokeColor: this.settingsStore.settings.always_border_caption_text,\n // 背景色\n forceBackgroundColor: (() => {\n if (this.settingsStore.settings.specify_caption_opacity === true) {\n const opacity = this.settingsStore.settings.caption_opacity;\n return `rgba(0, 0, 0, ${opacity})`;\n } else {\n return null;\n }\n })(),\n // DRCS 文字を対応する Unicode 文字に置換\n drcsReplacement: true,\n // 高解像度の字幕 Canvas を取得できるように\n enableRawCanvas: true,\n // 縁取りに strokeText API を利用\n useStroke: true,\n // Unicode 領域の代わりに私用面の領域を利用 (Windows TV 系フォントのみ)\n usePUA: (() => {\n const font = this.settingsStore.settings.caption_font;\n const context = document.createElement('canvas').getContext('2d')!;\n context.font = '10px \"Rounded M+ 1m for ARIB\"';\n context.fillText('Test', 0, 0);\n context.font = `10px \"${font}\"`;\n context.fillText('Test', 0, 0);\n if (font.startsWith('Windows TV')) {\n return true;\n } else {\n return false;\n }\n })(),\n // 文字スーパーの PRA (内蔵音再生コマンド) のコールバックを指定\n PRACallback: async (index: number) => {\n\n // 設定で文字スーパーが無効なら実行しない\n if (this.settingsStore.settings.tv_show_superimpose === false) return;\n\n // index に応じた内蔵音を鳴らす\n // ref: https://ics.media/entry/200427/\n // ref: https://www.ipentec.com/document/javascript-web-audio-api-change-volume\n\n // 自動再生ポリシーに引っかかったなどで AudioContext が一時停止されている場合、一度 resume() する必要がある\n // resume() するまでに何らかのユーザーのジェスチャーが行われているはず…\n // なくても動くこともあるみたいだけど、念のため\n if (this.romsounds_context.state === 'suspended') {\n await this.romsounds_context.resume();\n }\n\n // index で指定された音声データを読み込み\n const buffer_source_node = this.romsounds_context.createBufferSource();\n buffer_source_node.buffer = this.romsounds_buffers[index];\n\n // GainNode につなげる\n const gain_node = this.romsounds_context.createGain();\n buffer_source_node.connect(gain_node);\n\n // 出力につなげる\n gain_node.connect(this.romsounds_context.destination);\n\n // 音量を元の wav の3倍にする (1倍だと結構小さめ)\n gain_node.gain.value = 3;\n\n // 再生開始\n buffer_source_node.start(0);\n },\n }\n },\n // 字幕\n subtitle: {\n type: 'aribb24', // aribb24.js を有効化\n }\n });\n\n // デバッグ用にプレイヤーインスタンスも window 直下に入れる\n (window as any).player = this.player;\n\n // プレイヤー側のコントロール非表示タイマーを無効化(上書き)\n // 無効化しておかないと、controlDisplayTimer() と競合してしまう\n // 上書き元のコードは https://github.com/tsukumijima/DPlayer/blob/master/src/js/controller.js#L387-L395 にある\n this.player.controller.setAutoHide = (time: number) => {};\n\n // ニコニコ実況セッションを初期化し、随時コメントを受信できるようにする\n // 初期化以降の処理はすべて LiveCommentManager に任せる\n (this.$refs.Comment as InstanceType).initSession(this.player, this.channelsStore.channel_id);\n\n // ***** コメント送信時のイベントハンドラー *****\n\n // コメントが送信されたときに、プレイヤーからのコメント送信から間もないかどうかのフラグを立てる (0.1秒後に解除する)\n // コメントを送信するとコメント入力フォームへのフォーカスが外れるため、ページ全体の keydown イベントでは\n // Enter キーの押下がコメント送信由来のイベントかキャプチャ拡大表示由来のイベントかを判断できない\n // そこで、コメント入力フォームフォーカス中に Enter キーが押された場合(=コメント送信時)に 0.1 秒間フラグを立てることで、\n // ショートカットキーハンドラーがコメント送信由来のイベントであることを判定できるようにしている\n this.player.template.commentInput.addEventListener('keydown', (event) => {\n if (event.code === 'Enter') {\n this.is_comment_send_just_did = true;\n setTimeout(() => this.is_comment_send_just_did = false, 100);\n }\n });\n\n // 「コメント送信後にコメント入力フォームを閉じる」がオフになっている時のために、プレイヤー側のコメント送信関数を上書き\n // 上書き部分以外の処理内容は概ね https://github.com/tsukumijima/DPlayer/blob/master/src/js/comment.js に準じる\n this.player.comment!.send = () => {\n\n if (this.player === null) {\n return; // 復旧不可能 (発生しないはずだが、書いとかないと TypeScript に怒られる)\n }\n\n // コメント入力フォームへのフォーカスを外す (「コメント送信後にコメント入力フォームを閉じる」がオンのときだけ)\n if (this.settingsStore.settings.close_comment_form_after_sending === true) {\n this.player.template.commentInput.blur();\n }\n\n // 空コメントを弾く\n if (!this.player.template.commentInput.value.replace(/^\\s+|\\s+$/g, '')) {\n this.player.notice(this.player.tran('Please input danmaku content!'));\n return;\n }\n\n // コメントを送信\n this.player.danmaku!.send(\n {\n text: this.player.template.commentInput.value,\n color: this.player.container.\n querySelector('.dplayer-comment-setting-color input:checked')!.value,\n type: this.player.container.\n querySelector('.dplayer-comment-setting-type input:checked')!.value as DPlayerType.DanmakuType,\n size: this.player.container.\n querySelector('.dplayer-comment-setting-size input:checked')!.value as DPlayerType.DanmakuSize,\n },\n // 送信完了後にコメント入力フォームを閉じる ([コメント送信後にコメント入力フォームを閉じる] がオンのときだけ)\n () => {\n if (this.settingsStore.settings.close_comment_form_after_sending === true) {\n this.player !== null && this.player.comment!.hide();\n }\n },\n true,\n );\n\n // 重複送信を防ぐ\n this.player.template.commentInput.value = '';\n };\n\n // ***** 設定パネルのショートカット一覧へのリンクのイベントハンドラー *****\n\n // 設定パネルにショートカット一覧を表示するリンクを動的に追加する\n // タッチデバイスでは実行しない\n const is_touch_device = /iPhone|iPad|iPod|Macintosh|Android|Mobile/i.test(navigator.userAgent) && 'ontouchend' in document;\n if (is_touch_device === false) {\n this.player.template.settingOriginPanel.insertAdjacentHTML('beforeend', `\n
\n キーボードショートカット\n
\n \n \n \n
\n
`);\n\n // 設定パネルの高さを再設定\n const settingOriginPanelHeight = this.player.template.settingOriginPanel.scrollHeight;\n this.player.template.settingBox.style.clipPath = `inset(calc(100% - ${settingOriginPanelHeight}px) 0 0 round 7px)`;\n\n // 設定パネルのショートカット一覧を表示するリンクがクリックされたときのイベント\n // リアクティブではないので、手動でやらないといけない…\n this.$el.querySelector('.dplayer-setting-keyboard-shortcut')!.addEventListener('click', () => {\n this.player?.setting.hide(); // 設定パネルを閉じる\n this.shortcut_key_modal = true;\n });\n }\n\n // ***** フルスクリーンのイベントハンドラー *****\n\n // フルスクリーンにするコンテナ要素(ページ全体)\n const fullscreen_container = document.querySelector('.v-application')!;\n this.fullscreen_handler = () => {\n this.is_fullscreen = this.player?.fullScreen.isFullScreen() === true;\n };\n if (fullscreen_container.onfullscreenchange !== undefined) {\n fullscreen_container.addEventListener('fullscreenchange', this.fullscreen_handler);\n } else {\n fullscreen_container.addEventListener('webkitfullscreenchange', this.fullscreen_handler);\n }\n\n // DPlayer のフルスクリーン関係のメソッドを無理やり上書きし、KonomiTV の UI と統合する\n // 上書き元のコードは https://github.com/tsukumijima/DPlayer/blob/master/src/js/fullscreen.js にある\n // フルスクリーンかどうか\n this.player.fullScreen.isFullScreen = (type?: DPlayerType.FullscreenType) => {\n return !!(document.fullscreenElement || document.webkitFullscreenElement);\n };\n // フルスクリーンをリクエスト\n this.player.fullScreen.request = (type?: DPlayerType.FullscreenType) => {\n if (this.player === null) return;\n\n // すでにフルスクリーンだったらキャンセルする\n if (this.player.fullScreen.isFullScreen()) {\n this.player.fullScreen.cancel();\n return;\n }\n\n // フルスクリーンをリクエスト\n // Safari は webkit のベンダープレフィックスが必要\n fullscreen_container.requestFullscreen = fullscreen_container.requestFullscreen || fullscreen_container.webkitRequestFullscreen;\n if (fullscreen_container.requestFullscreen) {\n fullscreen_container.requestFullscreen();\n } else {\n // フルスクリーンがサポートされていない場合はエラーを表示\n this.player.notice('iPhone Safari は動画のフルスクリーン表示に対応していません。');\n return;\n }\n\n // 画面の向きを横に固定 (Screen Orientation API がサポートされている場合)\n if (screen.orientation) {\n screen.orientation.lock('landscape').catch(() => {});\n }\n };\n // フルスクリーンをキャンセル\n this.player.fullScreen.cancel = (type?: DPlayerType.FullscreenType) => {\n\n // フルスクリーンを終了\n // Safari は webkit のベンダープレフィックスが必要\n document.exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen;\n if (document.exitFullscreen) {\n document.exitFullscreen();\n }\n\n // 画面の向きの固定を解除\n if (screen.orientation) {\n screen.orientation.unlock();\n }\n };\n\n // ***** 再生/停止/画質切り替え時のイベントハンドラー *****\n\n // 再生/停止されたとき\n // 通知バーからの制御など、画面から以外の外的要因で再生/停止が行われる事もある\n const on_play_or_pause = () => {\n\n // まだ設定パネルが表示されていたら非表示にする\n this.player?.setting.hide();\n\n // コントロールを表示する\n this.controlDisplayTimer();\n };\n this.player.on('play', on_play_or_pause);\n this.player.on('pause', on_play_or_pause);\n\n // 画質の切り替えが開始されたときのイベント\n this.player.on('quality_start', () => {\n\n // ローディング中の背景画像をランダムで設定\n this.background_url = PlayerUtils.generatePlayerBackgroundURL();\n\n // イベントソースを閉じる\n if (this.eventsource !== null) {\n this.eventsource.close();\n this.eventsource = null;\n }\n\n // 新しい EventSource を作成\n // 画質ごとにイベント API は異なるため、一度破棄してから作り直す\n this.initEventHandler();\n });\n\n // 停止状態でかつ再生時間からバッファが 30 秒以上離れていないかを監視し、そうなっていたら強制的にシークする\n // mpegts.js の仕様上、MSE に未再生のバッファがたまり過ぎると SourceBuffer が追加できなくなるため、強制的に接続が切断されてしまう\n // LL-HLS 再生時も、ずっと停止したままだとプレイリストやセグメントに HTTP リクエストされなくなり、サーバー側でタイムアウトさせられてしまう\n // mpegts.js 再生時は 60 秒、LL-HLS 再生時は 30 秒おきに監視する (LL-HLS 再生時はバッファの状態に関わらずシークする)\n if (this.is_mpegts_supported === true) {\n this.interval_ids.push(window.setInterval(() => {\n if (this.player === null) return;\n if ((this.player.video.paused && this.player.video.buffered.length >= 1) &&\n (this.player.video.buffered.end(0) - this.player.video.currentTime > 30)) {\n this.player.sync();\n }\n }, 60 * 1000));\n } else {\n this.interval_ids.push(window.setInterval(() => {\n if (this.player === null) return;\n if (this.player.video.paused) {\n this.player.sync();\n }\n }, 30 * 1000));\n }\n\n // ***** 文字スーパーのイベントハンドラー *****\n\n (async () => {\n\n // 文字スーパーが初期化されるまで待つ\n if (this.player === null) return;\n while (this.player.plugins.aribb24Superimpose === undefined) {\n await Utils.sleep(0.1); // 0.1 秒待つ\n }\n\n // 設定で文字スーパーが有効\n // 字幕が非表示の場合でも、文字スーパーは表示する\n if (this.settingsStore.settings.tv_show_superimpose === true) {\n this.player.plugins.aribb24Superimpose.show();\n this.player.on('subtitle_hide', () => {\n this.player?.plugins.aribb24Superimpose!.show();\n });\n // 設定で文字スーパーが無効\n } else {\n this.player.plugins.aribb24Superimpose.hide();\n this.player.on('subtitle_show', () => {\n this.player?.plugins.aribb24Superimpose!.hide();\n });\n }\n\n })();\n },\n\n // イベントハンドラーを初期化する\n initEventHandler() {\n\n // ***** プレイヤー再生開始時のイベントハンドラー *****\n\n if (this.player === null) return;\n\n // 必ず最初はローディング状態とする\n this.is_loading = true;\n\n // 音量を 0 に設定\n this.player.video.volume = 0;\n\n // video 要素の crossOrigin 属性を 'anonymous' に設定\n // これを設定しないと、クロスオリジンの場合にキャプチャができない\n this.player.video.crossOrigin = 'anonymous';\n\n // mpegts.js 再生時のみ、mpegts.js のログハンドラーを設定する\n if (this.is_mpegts_supported === true && this.player.plugins.mpegts !== undefined) {\n this.player.plugins.mpegts.on(mpegts.Events.ERROR, async (error_type: mpegts.ErrorTypes, detail: mpegts.ErrorDetails) => {\n // 再生中にエラーが発生した場合\n // ワークアラウンドとして通知した後にページをリロードする\n // TODO: ロジックを整理してストリーミングを再起動できるようにする\n this.player.notice(`再生中にエラーが発生しました。(${error_type}: ${detail}) 3秒後にリロードします。`, -1);\n await Utils.sleep(3);\n location.reload();\n });\n // LL-HLS 再生時は、error イベントを監視してエラーが発生したらページをリロードする\n } else if (this.is_mpegts_supported === false) {\n this.player.on('error', async () => {\n this.player.notice(`再生中にエラーが発生しました。(${this.player.video.error.code}: ${this.player.video.error.message}) 3秒後にリロードします。`, -1);\n await Utils.sleep(3);\n location.reload();\n });\n }\n\n // LL-HLS 再生時のみ、ローディングが終わるまでは表示上再生状態を維持する\n // play() が正常に実行できればいいのだが、Safari の自動再生制限により失敗することがあるので、\n // その際はアイコンの HTML を書き換えたりして強制的に再生状態にする (苦肉の策)\n if (this.is_mpegts_supported === false) {\n const force_play = () => {\n this.player?.video.play().catch(() => {\n console.warn('HTMLVideoElement.play() rejected. run fallback.');\n const pause_icon = '';\n this.player!.template.playButton.innerHTML = pause_icon;\n this.player!.template.mobilePlayButton.innerHTML = pause_icon;\n this.player!.container.classList.remove('dplayer-paused');\n this.player!.container.classList.add('dplayer-playing');\n this.player!.danmaku!.play();\n });\n // ローディング表示が消えたタイミングでイベントを登録解除\n if (this.is_loading === false) {\n this.player?.video.removeEventListener('pause', force_play);\n return;\n }\n };\n this.player.video.addEventListener('pause', force_play);\n }\n\n // 再生バッファを調整し、再生準備ができた段階でプレイヤーの背景を非表示にするイベントを登録\n // 実際に再生可能になるのを待ってから実行する\n // 画質切り替え時にも実行する必要があるので、あえてこの位置に記述している\n const on_canplay = async () => {\n\n // 自分自身のイベントを登録解除 (重複実行を避ける)\n if (this.player === null) return;\n this.player.video.oncanplay = null;\n this.player.video.oncanplaythrough = null;\n\n // mpegts.js 利用時のみ実行\n if (this.is_mpegts_supported === true) {\n\n // 再生バッファ調整のため、一旦停止させる\n // this.player.video.pause() を使うとプレイヤーの UI アイコンが停止してしまうので、代わりに playbackRate を使う\n this.player.video.playbackRate = 0;\n\n // 再生バッファを取得する (取得に失敗した場合は 0 を返す)\n const get_playback_buffer_sec = (): number => {\n let buffered_end = 0;\n if (this.player.video.buffered.length >= 1) {\n buffered_end = this.player.video.buffered.end(0);\n }\n return (Math.round((buffered_end - this.player.video.currentTime) * 1000) / 1000);\n };\n\n // 低遅延モードであれば低遅延向けの再生バッファを、そうでなければ通常の再生バッファをセット (秒単位)\n const playback_buffer_sec = this.settingsStore.settings.tv_low_latency_mode ?\n PLAYBACK_BUFFER_SEC_LOW_LATENCY : PLAYBACK_BUFFER_SEC;\n\n // 再生バッファが playback_buffer_sec を超えるまで 0.1 秒おきに再生バッファをチェックする\n // 再生バッファが playback_buffer_sec を切ると再生が途切れやすくなるので (特に動きの激しい映像)、\n // 再生開始までの時間を若干犠牲にして、再生バッファの調整と同期に時間を割く\n // playback_buffer_sec の値は mpegts.js に渡す liveSyncTargetLatency プロパティに渡す値と共通\n let current_playback_buffer_sec = get_playback_buffer_sec();\n while (current_playback_buffer_sec < playback_buffer_sec) {\n await Utils.sleep(0.1);\n current_playback_buffer_sec = get_playback_buffer_sec();\n }\n\n // 再生開始\n this.player.video.playbackRate = 1;\n }\n\n const recover = async () => {\n await Utils.sleep(0.5);\n // この時点で映像が停止している場合、復旧を試みる\n if (this.player?.video.readyState < 3) {\n console.log('player.video.readyState < HAVE_FUTURE_DATA. trying to recover.');\n this.player?.video.pause();\n await Utils.sleep(0.1);\n this.player?.video.play().catch(() => {\n console.warn('HTMLVideoElement.play() rejected. paused.');\n this.player?.pause();\n });\n }\n };\n\n // 再生が一時的に止まってバッファリングしているとき/再び再生されはじめたときのイベント\n // バッファリングの Progress Circular の表示を制御する\n // 同期が終わってからの方が都合が良い\n this.player.video.addEventListener('waiting', () => this.is_video_buffering = true);\n this.player.video.addEventListener('playing', () => {\n this.is_video_buffering = false;\n recover();\n });\n\n // ローディング状態を解除し、映像を表示する\n this.is_loading = false;\n\n // バッファリング中の Progress Circular を非表示にする\n this.is_video_buffering = false;\n recover();\n\n if (this.channelsStore.channel.current.is_radiochannel) {\n // ラジオチャンネルでは引き続き映像の代わりとして背景画像を表示し続ける\n this.is_background_display = true;\n } else {\n // 背景画像をフェードアウト\n this.is_background_display = false;\n }\n\n // 再生開始時に音量を徐々に上げる\n // いきなり再生されるよりも体験が良い\n const current_volume = this.player.user.get('volume');\n while ((this.player.video.volume + 0.05) < current_volume) {\n // 小数第2位以下を切り捨てて、浮動小数の誤差で 1 (100%) を微妙に超えてしまいエラーになるのを避ける\n this.player.video.volume = Utils.mathFloor(this.player.video.volume + 0.05, 2);\n await Utils.sleep(0.02);\n }\n this.player.video.volume = current_volume;\n };\n this.player.video.oncanplay = on_canplay;\n this.player.video.oncanplaythrough = on_canplay;\n\n // ***** KonomiTV サーバーのイベント API のイベントハンドラー *****\n\n // EventSource を作成\n const eventsource_url = (this.player!.quality!.url as string).replace('/mpegts', '/events').replace(/\\/ll-hls.*/, '/events');\n this.eventsource = new EventSource(eventsource_url);\n\n // 初回接続時のイベント\n this.eventsource.addEventListener('initial_update', (event_raw: MessageEvent) => {\n\n // イベントを取得\n const event = JSON.parse(event_raw.data);\n console.log(`[initial_update] Status: ${event.status} / Detail: ${event.detail}`);\n\n // ステータスごとに処理を振り分け\n switch (event.status) {\n\n // Status: Standby\n case 'Standby': {\n\n // バッファリング中の Progress Circular を表示\n this.is_video_buffering = true;\n\n // プレイヤーの背景を表示する\n this.is_background_display = true;\n break;\n }\n }\n });\n\n // ステータスが更新されたときのイベント\n this.eventsource.addEventListener('status_update', async (event_raw: MessageEvent) => {\n\n // イベントを取得\n if (this.player === null) return;\n const event = JSON.parse(event_raw.data);\n console.log(`[status_update] Status: ${event.status} / Detail: ${event.detail}`);\n\n // 視聴者数を更新\n this.channelsStore.updateChannel(this.channelsStore.channel_id, {\n ...this.channelsStore.channel.current,\n viewers: event.clients_count,\n });\n\n // ステータスごとに処理を振り分け\n switch (event.status) {\n\n // Status: Standby\n case 'Standby': {\n\n // ステータス詳細をプレイヤーに表示\n if (!this.player.template.notice.textContent!.includes('画質を')) { // 画質切り替えの表示を上書きしない\n this.player.notice(event.detail, -1);\n }\n\n // バッファリング中の Progress Circular を表示\n this.is_video_buffering = true;\n\n // プレイヤーの背景を表示する\n this.is_background_display = true;\n break;\n }\n\n // Status: ONAir\n case 'ONAir': {\n\n // ステータス詳細をプレイヤーから削除\n if (!this.player.template.notice.textContent!.includes('画質を')) { // 画質切り替えの表示を上書きしない\n this.player.notice(this.player.template.notice.textContent!, 0.000001);\n }\n\n // LL-HLS ストリーミング時のみ、このタイミングで映像をロードして再生を開始する\n if (this.is_mpegts_supported === false) {\n this.player.video.load();\n this.player.video.play();\n on_canplay();\n }\n\n // 再生が開始される前にチャンネルを切り替えた際にコメントが流れない不具合のワークアラウンド\n if (this.player.container.classList.contains('dplayer-paused')) {\n this.player.container.classList.remove('dplayer-paused');\n this.player.container.classList.add('dplayer-playing');\n }\n\n // 前のプレイヤーインスタンスの Picture-in-Picture ウインドウが残っている場合、終了させてからもう一度切り替える\n // チャンネル切り替えが完了しても前の Picture-in-Picture ウインドウは再利用されないため、一旦終了させるしかない\n if (document.pictureInPictureElement) {\n document.exitPictureInPicture();\n this.player.video.requestPictureInPicture();\n }\n break;\n }\n\n // Status: Idling\n case 'Idling': {\n\n // 本来誰も視聴していないことを示す Idling ステータスを受信している場合、何らかの理由で\n // ストリーミング API への接続が切断された可能性が高いので、ワークアラウンドとして通知した後にページをリロードする\n // TODO: ロジックを整理してストリーミングを再起動できるようにする\n this.player.notice('ストリーミング接続が切断されました。3秒後にリロードします。', -1);\n await Utils.sleep(3);\n location.reload();\n\n break;\n }\n\n // Status: Restart\n case 'Restart': {\n\n // 「ライブストリームは Offline です。」のステータス詳細を受信すること自体が不正な状態\n // ストリーミング API への接続が切断された可能性が高いので、ワークアラウンドとして通知した後にページをリロードする\n // TODO: ロジックを整理してストリーミングを再起動できるようにする\n if (event.detail === 'ライブストリームは Offline です。') {\n this.player.notice('ストリーミング接続が切断されました。3秒後にリロードします。', -1);\n await Utils.sleep(3);\n location.reload();\n }\n\n // ステータス詳細をプレイヤーに表示\n this.player.notice(event.detail, -1);\n\n // プレイヤーを再起動する\n this.player.switchVideo({\n url: this.player.quality!.url,\n type: this.player.quality!.type,\n });\n\n // 再起動しただけでは自動再生されないので、明示的に\n this.player.play();\n\n // バッファリング中の Progress Circular を表示\n this.is_video_buffering = true;\n\n // プレイヤーの背景を表示する\n this.is_background_display = true;\n break;\n }\n\n // Status: Offline\n // 基本的に Offline は放送休止中やエラーなどで復帰の見込みがない状態\n case 'Offline': {\n\n if (this.player !== null) {\n\n // ステータス詳細をプレイヤーに表示\n // 動画の読み込みエラーが送出された時にメッセージを上書きする\n this.player.notice(event.detail, -1);\n this.player.video.onerror = () => {\n this.player!.notice(event.detail, -1);\n this.player!.video.onerror = null;\n };\n\n // 描画されたコメントをクリア\n this.player?.danmaku?.clear();\n\n // 動画を停止する\n this.player.video.pause();\n }\n\n // イベントソースを閉じる(復帰の見込みがないため)\n if (this.eventsource !== null) {\n this.eventsource.close();\n this.eventsource = null;\n }\n\n // プレイヤーの背景を表示する\n this.is_background_display = true;\n\n // バッファリング中の Progress Circular を非表示にする\n this.is_loading = false;\n this.is_video_buffering = false;\n break;\n }\n }\n });\n\n // ステータス詳細が更新されたときのイベント\n this.eventsource.addEventListener('detail_update', (event_raw: MessageEvent) => {\n\n // イベントを取得\n if (this.player === null) return;\n const event = JSON.parse(event_raw.data);\n console.log(`[detail_update] Status: ${event.status} Detail:${event.detail}`);\n\n // 視聴者数を更新\n this.channelsStore.updateChannel(this.channelsStore.channel_id, {\n ...this.channelsStore.channel.current,\n viewers: event.clients_count,\n });\n\n // ステータスごとに処理を振り分け\n switch (event.status) {\n\n // Status: Standby\n case 'Standby': {\n\n // ステータス詳細をプレイヤーに表示\n this.player.notice(event.detail, -1);\n\n // プレイヤーの背景を表示する\n if (!this.is_background_display) {\n this.is_background_display = true;\n }\n break;\n }\n }\n });\n\n // クライアント数(だけ)が更新されたときのイベント\n this.eventsource.addEventListener('clients_update', (event_raw: MessageEvent) => {\n\n // イベントを取得\n const event = JSON.parse(event_raw.data);\n\n // 視聴者数を更新\n this.channelsStore.updateChannel(this.channelsStore.channel_id, {\n ...this.channelsStore.channel.current,\n viewers: event.clients_count,\n });\n });\n },\n\n // ショートカットキーを初期化する\n initShortcutKeyHandler() {\n\n const twitter_component = (this.$refs.Twitter as InstanceType);\n const tweet_form_element = twitter_component.$el.querySelector('.tweet-form__textarea');\n\n // IME 変換中の状態を保存する\n for (const element of document.querySelectorAll('input[type=text],input[type=search],textarea')) {\n element.addEventListener('compositionstart', () => this.is_ime_composing = true);\n element.addEventListener('compositionend', () => this.is_ime_composing = false);\n }\n\n // ショートカットキーハンドラー\n this.shortcut_key_handler = async (event: KeyboardEvent) => {\n\n const tag = document.activeElement.tagName.toUpperCase();\n const editable = document.activeElement.getAttribute('contenteditable');\n\n // 矢印キーのデフォルトの挙動(スクロール)を抑制\n // キーリピート周りで間引かれるイベントでも event.preventDefault() しないとスクロールしてしまうため、\n // 一番最初のタイミングでやっておく\n // input・textarea・contenteditable 状態の要素では実行しない\n if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.code) &&\n (tag !== 'INPUT' && tag !== 'TEXTAREA' && editable !== '' && editable !== 'true')) {\n event.preventDefault();\n }\n\n // キーリピート(押しっぱなし)状態の場合は基本実行しない\n // 押し続けると何度も同じ動作が実行されて大変な事になる…\n // ただ、キーリピートを使いたい場合もあるので、リピート状態をフラグとして保存する\n let is_repeat = false;\n if (event.repeat) is_repeat = true;\n\n // キーリピート状態は event.repeat を見る事でだいたい検知できるが、最初の何回かは検知できないこともある\n // そこで、0.05 秒以内に連続して発火したキーイベントは間引きも兼ねて実行しない\n const now = Utils.time();\n if (now - this.shortcut_key_pressed_at < 0.05) return;\n this.shortcut_key_pressed_at = now; // 最終押下時刻を更新\n\n // 無名関数の中で実行する\n const result = await (async (): Promise => {\n\n // ***** ツイート入力フォームにフォーカスを当てる/フォーカスを外す *****\n\n // ツイート入力フォームにフォーカスしているときもこのショートカットが動くようにする\n // 以降の if 文で textarea フォーカス時のイベントをすべて弾いてしまっているため、前に持ってきている\n // Tab キーに割り当てている関係で、IME 変換中は実行しない(IME 変換中に実行すると文字変換ができなくなる)\n if (((tag !== 'INPUT' && tag !== 'TEXTAREA' && editable !== '' && editable !== 'true') ||\n (document.activeElement === tweet_form_element)) && this.is_ime_composing === false) {\n if (event.code === 'Tab') {\n\n // ツイート入力フォームにフォーカスがすでに当たっていたら、フォーカスを外して終了\n if (document.activeElement === tweet_form_element) {\n tweet_form_element.blur();\n return true;\n }\n\n // パネルを開く\n this.is_panel_display = true;\n\n // どのタブを開いていたかに関係なく Twitter タブに切り替える\n this.tv_panel_active_tab = 'Twitter';\n\n // ツイート入力フォームの textarea 要素にフォーカスを当てる\n tweet_form_element.focus();\n\n // フォーカスを当てると勝手に横方向にスクロールされてしまうので、元に戻す\n this.$el.scrollLeft = 0;\n\n window.setTimeout(() => {\n\n // 他のタブから切り替えると一発でフォーカスが当たらないことがあるので、ちょっとだけ待ってから念押し\n // $nextTick() だと上手くいかなかった…\n tweet_form_element.focus();\n\n // フォーカスを当てると勝手に横方向にスクロールされてしまうので、元に戻す\n this.$el.scrollLeft = 0;\n\n }, 100); // 0.1秒\n\n return true;\n }\n }\n\n // ***** ツイートを送信する *****\n\n // ツイート入力フォームにフォーカスしているときもこのショートカットが動くようにする\n // Twitter タブ以外を開いているときは実行しない\n // 以降の if 文で textarea フォーカス時のイベントをすべて弾いてしまっているため、前に持ってきている\n if (((tag !== 'INPUT' && tag !== 'TEXTAREA' && editable !== '' && editable !== 'true') ||\n (document.activeElement === tweet_form_element)) &&\n this.tv_panel_active_tab === 'Twitter' &&\n this.is_ime_composing === false) {\n // (Ctrl or Cmd or Shift) + Enter\n // Shift + Enter は隠し機能(間違えたとき用)\n if ((event.ctrlKey || event.metaKey || event.shiftKey) && event.code === 'Enter') {\n twitter_component.$el.querySelector('.tweet-button')!.click();\n return true;\n }\n }\n\n // ***** コメント入力フォームを閉じる *****\n\n // プレイヤーが初期化されていない時・Shift / Alt キーが一緒に押された時に作動しないように\n if (this.player !== null && !event.shiftKey && !event.altKey) {\n\n // コメント入力フォームが表示されているときのみ\n if (this.player.template.controller.classList.contains('dplayer-controller-comment')) {\n // Ctrl or Cmd + M\n if ((event.ctrlKey || event.metaKey) && event.code === 'KeyM') {\n this.player.comment!.hide();\n return true;\n }\n }\n }\n\n // input・textarea・contenteditable 状態の要素でなければ\n // 文字入力中にショートカットキーが作動してしまわないように\n if (tag !== 'INPUT' && tag !== 'TEXTAREA' && editable !== '' && editable !== 'true') {\n\n // キーリピートでない時・Ctrl / Cmd キーが一緒に押された時に作動しないように\n // チャンネル選局のキーボードショートカットを Alt or Option + 数字キー/テンキーに変更する設定が有効なときは、\n // Alt or Option キーが押されていることを条件に追加する\n if (is_repeat === false && !event.ctrlKey && !event.metaKey &&\n (this.settingsStore.settings.tv_channel_selection_requires_alt_key === false || (event.altKey))) {\n\n // ***** 数字キーでチャンネルを切り替える *****\n\n // Shift キーが同時押しされていたら BS チャンネルの方を選局する\n const switch_channel_type = (event.shiftKey) ? 'BS' : 'GR';\n\n // 1~9キー\n let switch_remocon_id: number | null = null;\n if (event.code === 'Digit1' || event.code === 'Digit2' || event.code === 'Digit3' ||\n event.code === 'Digit4' || event.code === 'Digit5' || event.code === 'Digit6' ||\n event.code === 'Digit7' || event.code === 'Digit8' || event.code === 'Digit9') {\n switch_remocon_id = Number(event.code.replace('Digit', ''));\n }\n // 0キー: 10に割り当て\n if (event.code === 'Digit0') switch_remocon_id = 10;\n // -キー: 11に割り当て\n if (event.code === 'Minus') switch_remocon_id = 11;\n // ^キー: 12に割り当て\n if (event.code === 'Equal') switch_remocon_id = 12;\n // 1~9キー (テンキー)\n if (event.code === 'Numpad1' || event.code === 'Numpad2' || event.code === 'Numpad3' ||\n event.code === 'Numpad4' || event.code === 'Numpad5' || event.code === 'Numpad6' ||\n event.code === 'Numpad7' || event.code === 'Numpad8' || event.code === 'Numpad9') {\n switch_remocon_id = Number(event.code.replace('Numpad', ''));\n }\n // 0キー (テンキー): 10に割り当て\n if (event.code === 'Numpad0') switch_remocon_id = 10;\n\n // この時点でリモコン番号が取得できていたら実行\n if (switch_remocon_id !== null) {\n\n // 切り替え先のチャンネルを取得する\n const switch_channel = this.channelsStore.getChannelByRemoconID(switch_channel_type, switch_remocon_id);\n\n // チャンネルが取得できていれば、ルーティングをそのチャンネルに置き換える\n // 押されたキーに対応するリモコン番号のチャンネルがない場合や、現在と同じチャンネル ID の場合は何も起こらない\n if (switch_channel !== null && switch_channel.channel_id !== this.channelsStore.channel_id) {\n await this.$router.push({path: `/tv/watch/${switch_channel.channel_id}`});\n return true;\n }\n }\n }\n\n // キーリピートでない時・Ctrl / Cmd / Shift / Alt キーが一緒に押された時に作動しないように\n if (is_repeat === false && !event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey) {\n\n // ***** キーボードショートカットの一覧を表示する *****\n\n // /(?)キー: キーボードショートカットの一覧を表示する\n if (event.code === 'Slash') {\n this.shortcut_key_modal = !this.shortcut_key_modal;\n return true;\n }\n\n // ***** パネルのタブを切り替える *****\n\n // Pキー: パネルの表示切り替え\n if (event.code === 'KeyP') {\n this.is_panel_display = !this.is_panel_display;\n return true;\n }\n // Kキー: 番組情報タブ\n if (event.code === 'KeyK') {\n this.tv_panel_active_tab = 'Program';\n return true;\n }\n // Lキー: チャンネルタブ\n if (event.code === 'KeyL') {\n this.tv_panel_active_tab = 'Channel';\n return true;\n }\n // ;(+)キー: コメントタブ\n if (event.code === 'Semicolon') {\n this.tv_panel_active_tab = 'Comment';\n return true;\n }\n // :(*)キー: Twitterタブ\n if (event.code === 'Quote') {\n this.tv_panel_active_tab = 'Twitter';\n return true;\n }\n\n // ***** Twitter タブ内のタブを切り替える *****\n\n // [(「): ツイート検索タブ\n if (event.code === 'BracketRight') {\n twitter_component.twitter_active_tab = 'Search';\n return true;\n }\n // ](」): タイムラインタブ\n if (event.code === 'Backslash') {\n twitter_component.twitter_active_tab = 'Timeline';\n return true;\n }\n // \\(¥)キー: キャプチャタブ\n if (event.code === 'IntlRo') {\n twitter_component.twitter_active_tab = 'Capture';\n return true;\n }\n }\n\n // Twitter タブ内のキャプチャタブが表示されている & Ctrl / Cmd / Shift / Alt のいずれも押されていないときだけ\n // キャプチャタブが表示されている時は、プレイヤー操作側の矢印キー/スペースキーのショートカットは動作しない(キーが重複するため)\n if (this.tv_panel_active_tab === 'Twitter' && twitter_component.twitter_active_tab === 'Capture' &&\n (!event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey)) {\n\n // ***** キャプチャにフォーカスする *****\n\n if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.code)) {\n\n // キャプチャリストに一枚もキャプチャがない\n if (twitter_component.captures.length === 0) return false;\n\n // まだどのキャプチャにもフォーカスされていない場合は、一番新しいキャプチャにフォーカスして終了\n if (twitter_component.captures.some(capture => capture.focused === true) === false) {\n twitter_component.captures[twitter_component.captures.length - 1].focused = true;\n return true;\n }\n\n // 現在フォーカスされているキャプチャのインデックスを取得\n const focused_capture_index = twitter_component.captures.findIndex(capture => capture.focused === true);\n\n // ↑キー: 2つ前のキャプチャにフォーカスする\n // キャプチャリストは2列で並んでいるので、2つ後のキャプチャが現在フォーカスされているキャプチャの直上になる\n if (event.code === 'ArrowUp') {\n // 2つ前のキャプチャがないなら実行しない\n if (focused_capture_index - 2 < 0) return false;\n twitter_component.captures[focused_capture_index - 2].focused = true;\n }\n\n // ↓キー: 2つ後のキャプチャにフォーカスする\n // キャプチャリストは2列で並んでいるので、2つ後のキャプチャが現在フォーカスされているキャプチャの直下になる\n if (event.code === 'ArrowDown') {\n // 2つ後のキャプチャがないなら実行しない\n if (focused_capture_index + 2 > (twitter_component.captures.length - 1)) return false;\n twitter_component.captures[focused_capture_index + 2].focused = true;\n }\n\n // ←キー: 1つ前のキャプチャにフォーカスする\n if (event.code === 'ArrowLeft') {\n // 1つ前のキャプチャがないなら実行しない\n if (focused_capture_index - 1 < 0) return false;\n twitter_component.captures[focused_capture_index - 1].focused = true;\n }\n\n // ←キー: 1つ後のキャプチャにフォーカスする\n if (event.code === 'ArrowRight') {\n // 1つ後のキャプチャがないなら実行しない\n if (focused_capture_index + 1 > (twitter_component.captures.length - 1)) return false;\n twitter_component.captures[focused_capture_index + 1].focused = true;\n }\n\n // 現在フォーカスされているキャプチャのフォーカスを外す\n twitter_component.captures[focused_capture_index].focused = false;\n\n // 拡大表示のモーダルが開かれている場合は、フォーカスしたキャプチャをモーダルにセット\n // こうすることで、QuickLook みたいな挙動になる\n const focused_capture = twitter_component.captures.find(capture => capture.focused === true);\n if (twitter_component.zoom_capture_modal === true) {\n twitter_component.zoom_capture = focused_capture;\n }\n\n // 現在フォーカスされているキャプチャが見える位置までスクロール\n // block: 'nearest' の指定で、上下どちらにスクロールしてもフォーカスされているキャプチャが常に表示されるようになる\n const focused_capture_element =\n twitter_component.$el.querySelector(`img[src=\"${focused_capture.image_url}\"]`).parentElement;\n if (is_repeat) {\n // キーリピート状態ではスムーズスクロールがフォーカスの移動に追いつけずスクロールの挙動がおかしくなるため、\n // スムーズスクロールは無効にしてある\n focused_capture_element.scrollIntoView({block: 'nearest', inline: 'nearest', behavior: 'auto'});\n } else {\n focused_capture_element.scrollIntoView({block: 'nearest', inline: 'nearest', behavior: 'smooth'});\n }\n return true;\n }\n\n // ***** キャプチャを拡大表示する/拡大表示を閉じる *****\n\n if (event.code === 'Enter') {\n\n // Enter キーの押下がプレイヤー側のコメント送信由来のイベントの場合は実行しない\n if (this.is_comment_send_just_did) return false;\n\n // すでにモーダルが開かれている場合は、どのキャプチャが拡大表示されているかに関わらず閉じる\n if (twitter_component.zoom_capture_modal === true) {\n twitter_component.zoom_capture_modal = false;\n return true;\n }\n\n // 現在フォーカスされているキャプチャを取得\n // まだどのキャプチャにもフォーカスされていない場合は実行しない\n const focused_capture = twitter_component.captures.find(capture => capture.focused === true);\n if (focused_capture === undefined) return false;\n\n // モーダルを開き、モーダルで拡大表示するキャプチャとしてセット\n twitter_component.zoom_capture = focused_capture;\n twitter_component.zoom_capture_modal = true;\n return true;\n }\n\n // ***** キャプチャを選択する/選択を解除する *****\n\n if (event.code === 'Space') {\n\n // 現在フォーカスされているキャプチャを取得\n // まだどのキャプチャにもフォーカスされていない場合は実行しない\n const focused_capture = twitter_component.captures.find(capture => capture.focused === true);\n if (focused_capture === undefined) return false;\n\n // 「キャプチャリスト内のキャプチャがクリックされたときのイベント」を呼ぶ\n // 選択されていなければ選択され、選択されていれば選択が解除される\n // キャプチャの枚数制限などはすべて clickCapture() の中で処理される\n twitter_component.clickCapture(focused_capture);\n return true;\n }\n }\n\n // ***** 上下キーでチャンネルを切り替える *****\n\n // キーリピートでない時・Ctrl / Cmd / Shift / Alt キーが一緒に押された時に作動しないように\n // キャプチャ関連のショートカットの後に持ってこないとキャプチャ関連のショートカットが動作しなくなる\n if (is_repeat === false && !event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey) {\n\n // ↑キー: 前のチャンネルに切り替え\n if (event.code === 'ArrowUp') {\n this.is_zapping = true;\n await this.$router.push({path: `/tv/watch/${this.channelsStore.channel.previous.channel_id}`});\n return true;\n }\n // ↓キー: 次のチャンネルに切り替え\n if (event.code === 'ArrowDown') {\n this.is_zapping = true;\n await this.$router.push({path: `/tv/watch/${this.channelsStore.channel.next.channel_id}`});\n return true;\n }\n }\n\n // ***** プレイヤーのショートカットキー *****\n\n // プレイヤーが初期化されていない時・Shift / Alt キーが一緒に押された時に作動しないように\n if (this.player !== null && !event.shiftKey && !event.altKey) {\n\n // Ctrl / Cmd + ↑キー: プレイヤーの音量を上げる\n if ((event.ctrlKey || event.metaKey) && event.code === 'ArrowUp') {\n this.player.volume(this.player.volume() + 0.05);\n return true;\n }\n // Ctrl / Cmd + ↓キー: プレイヤーの音量を下げる\n if ((event.ctrlKey || event.metaKey) && event.code === 'ArrowDown') {\n this.player.volume(this.player.volume() - 0.05);\n return true;\n }\n // Ctrl / Cmd + ←キー: 停止して0.5秒巻き戻し\n if ((event.ctrlKey || event.metaKey) && event.code === 'ArrowLeft') {\n if (this.player.video.paused === false) this.player.video.pause();\n this.player.video.currentTime = this.player.video.currentTime - 0.5;\n return true;\n }\n // Ctrl / Cmd + →キー: 停止して0.5秒早送り\n if ((event.ctrlKey || event.metaKey) && event.code === 'ArrowRight') {\n if (this.player.video.paused === false) this.player.video.pause();\n this.player.video.currentTime = this.player.video.currentTime + 0.5;\n return true;\n }\n }\n\n // プレイヤーが初期化されていない時・Ctrl / Cmd / Alt キーが一緒に押された時に作動しないように\n if (this.player !== null && !event.ctrlKey && !event.metaKey && !event.altKey) {\n\n // Shift + Spaceキー + キーリピートでない時 + Twitter タブ表示時 + キャプチャタブ表示時: 再生/停止\n if (event.shiftKey === true && event.code === 'Space' && is_repeat === false &&\n this.tv_panel_active_tab === 'Twitter' && twitter_component.twitter_active_tab === 'Capture') {\n this.player.toggle();\n return true;\n }\n }\n\n // プレイヤーが初期化されていない時・キーリピートでない時・Ctrl / Cmd / Alt キーが一緒に押された時に作動しないように\n if (this.player !== null && is_repeat === false && !event.ctrlKey && !event.metaKey && !event.altKey) {\n\n // Spaceキー: 再生/停止\n if (event.code === 'Space') {\n this.player.toggle();\n return true;\n }\n // Fキー: フルスクリーンの切り替え\n if (event.code === 'KeyF') {\n this.player.fullScreen.toggle();\n return true;\n }\n // Wキー: ライブストリームの同期\n if (event.code === 'KeyW') {\n this.player.sync();\n return true;\n }\n // Eキー: Picture-in-Picture の表示切り替え\n if (event.code === 'KeyE') {\n if (document.pictureInPictureEnabled) {\n this.player.template.pipButton.click();\n }\n return true;\n }\n // Sキー: 字幕の表示切り替え\n if (event.code === 'KeyS') {\n this.player.subtitle.toggle();\n if (!this.player.subtitle.container.classList.contains('dplayer-subtitle-hide')) {\n this.player.notice(`${this.player.tran('Show subtitle')}`);\n } else {\n this.player.notice(`${this.player.tran('Hide subtitle')}`);\n }\n return true;\n }\n // Dキー: コメントの表示切り替え\n if (event.code === 'KeyD') {\n this.player.template.showDanmaku.click();\n if (this.player.template.showDanmakuToggle.checked) {\n this.player.notice(`${this.player.tran('Show comment')}`);\n } else {\n this.player.notice(`${this.player.tran('Hide comment')}`);\n }\n return true;\n }\n // Cキー: 映像をキャプチャ\n if (event.code === 'KeyC') {\n await this.capture_handler.captureAndSave(false);\n return true;\n }\n // Vキー: 映像を実況コメントを付けてキャプチャ\n if (event.code === 'KeyV') {\n await this.capture_handler.captureAndSave(true);\n return true;\n }\n // Mキー: コメント入力フォームにフォーカス\n if (event.code === 'KeyM') {\n this.player.controller.show();\n this.player.comment.show();\n this.controlDisplayTimer();\n window.setTimeout(() => this.player.template.commentInput.focus(), 100);\n return true;\n }\n }\n }\n return false;\n })();\n\n // 無名関数を実行した後の戻り値が true ならショートカットキーの操作を実行したことになるので、デフォルトのキー操作を封じる\n if (result === true) {\n event.preventDefault();\n }\n };\n\n // ページ上でキーが押されたときのイベントを登録\n document.addEventListener('keydown', this.shortcut_key_handler);\n },\n\n // キャプチャ関連のイベントを初期化する\n initCaptureHandler() {\n\n // キャプチャハンドラーを初期化\n this.capture_handler = new CaptureHandler(this.player, (blob: Blob, filename: string) => {\n // キャプチャが撮られたら、随時 Twitter タブのキャプチャリストに追加する\n (this.$refs.Twitter as InstanceType).addCaptureList(blob, filename);\n });\n\n // キャプチャボタンがクリックされたときのイベント\n // ショートカットからのキャプチャでも同じイベントがトリガーされる\n const capture_button = this.$el.querySelector('.dplayer-icon.dplayer-capture-icon');\n capture_button.addEventListener('click', async () => {\n await this.capture_handler.captureAndSave(false);\n });\n\n // コメント付きキャプチャボタンがクリックされたときのイベント\n // ショートカットからのキャプチャでも同じイベントがトリガーされる\n const comment_capture_button = this.$el.querySelector('.dplayer-icon.dplayer-comment-capture-icon');\n comment_capture_button.addEventListener('click', async () => {\n await this.capture_handler.captureAndSave(true);\n });\n },\n\n\n // 再生セッションを破棄する\n // チャンネルを切り替える際に実行される\n async destroy(is_destroy_player = false, is_zapping_continuously = false) {\n\n // ニコニコ実況セッションを破棄し、コメント受信を終了する\n (this.$refs.Comment as InstanceType).destroy();\n\n // clearInterval() ですべての setInterval(), setTimeout() の実行を止める\n // clearInterval() と clearTimeout() は中身共通なので問題ない\n for (const interval_id of this.interval_ids) {\n window.clearInterval(interval_id);\n }\n\n // コントロール表示制御用タイマーを止める\n window.clearTimeout(this.control_interval_id);\n\n // interval_ids をクリア\n this.interval_ids = [];\n\n // 再びローディング状態にする\n this.is_loading = true;\n\n // プレイヤーの背景を隠す\n this.is_background_display = false;\n\n // プレイヤーの破棄を許可する\n if (this.player !== null) {\n this.player_can_be_destroyed = true;\n }\n\n // イベントソースを閉じる\n if (this.eventsource !== null) {\n this.eventsource.close();\n this.eventsource = null;\n }\n\n // 映像がフェードアウトするアニメーション (0.2秒) 分待ってから実行\n // この 0.2 秒の間に音量をフェードアウトさせる\n // なお、ザッピングでチャンネルを連続で切り替えている場合は実行しない (実行しても意味がないため)\n if (is_zapping_continuously === false) {\n const current_volume = this.player.user.get('volume');\n // 20回 (0.01秒おき) に分けて音量を下げる\n for (let i = 0; i < 20; i++) {\n await Utils.sleep(0.01);\n this.player.video.volume = current_volume * (1 - (i + 1) / 20);\n }\n }\n\n // is_destroy_player が true の時は、ここで DPlayer 自体を破棄する\n // false の時は次の initPlayer() が実行されるまで破棄されない\n // 次のプレイヤーの初期化の直前に前のプレイヤーを破棄することで、プレイヤーの HTML が消えることによるちらつきを防ぐ\n if (is_destroy_player === true && this.player !== null) {\n try {\n this.player.destroy();\n } catch (error) {\n // mpegts.js をうまく破棄できない場合\n if (this.player.plugins.mpegts !== undefined) {\n this.player.plugins.mpegts.destroy();\n }\n }\n this.player_can_be_destroyed = false;\n this.player = null;\n }\n }\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Watch.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Watch.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Watch.vue?vue&type=template&id=51acc2dc&scoped=true&\"\nimport script from \"./Watch.vue?vue&type=script&lang=ts&\"\nexport * from \"./Watch.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Watch.vue?vue&type=style&index=0&id=51acc2dc&prod&lang=scss&\"\nimport style1 from \"./Watch.vue?vue&type=style&index=1&id=51acc2dc&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"51acc2dc\",\n null\n \n)\n\nexport default component.exports","\nimport Vue from 'vue';\nimport VueRouter from 'vue-router';\n\nimport Utils from '@/utils';\nimport Login from '@/views/Login.vue';\nimport NotFound from '@/views/NotFound.vue';\nimport Register from '@/views/Register.vue';\nimport SettingsAccount from '@/views/Settings/Account.vue';\nimport SettingsCaption from '@/views/Settings/Caption.vue';\nimport SettingsCapture from '@/views/Settings/Capture.vue';\nimport SettingsGeneral from '@/views/Settings/General.vue';\nimport SettingsIndex from '@/views/Settings/Index.vue';\nimport SettingsJikkyo from '@/views/Settings/Jikkyo.vue';\nimport SettingsServer from '@/views/Settings/Server.vue';\nimport SettingsTwitter from '@/views/Settings/Twitter.vue';\nimport TVHome from '@/views/TV/Home.vue';\nimport TVWatch from '@/views/TV/Watch.vue';\n\nVue.use(VueRouter);\n\nconst router = new VueRouter({\n\n // History API モード\n mode: 'history',\n\n // ルーティングのベース URL\n base: process.env.BASE_URL,\n\n // ルーティング設定\n routes: [\n {\n path: '/',\n redirect: '/tv/',\n },\n {\n path: '/tv/',\n name: 'TV Home',\n component: TVHome,\n },\n {\n path: '/tv/watch/:channel_id',\n name: 'TV Watch',\n component: TVWatch,\n },\n {\n path: '/settings/',\n name: 'Settings Index',\n component: SettingsIndex,\n beforeEnter: (to, from, next) => {\n\n // スマホ縦画面・スマホ横画面・タブレット縦画面では設定一覧画面を表示する(画面サイズの関係)\n if (Utils.isSmartphoneVertical() || Utils.isSmartphoneHorizontal() || Utils.isTabletVertical()) {\n next(); // 通常通り遷移\n return;\n }\n\n // それ以外の画面サイズでは全般設定にリダイレクト\n next({path: '/settings/general/'});\n }\n },\n {\n path: '/settings/general',\n name: 'Settings General',\n component: SettingsGeneral,\n },\n {\n path: '/settings/caption',\n name: 'Settings Caption',\n component: SettingsCaption,\n },\n {\n path: '/settings/capture',\n name: 'Settings Capture',\n component: SettingsCapture,\n },\n {\n path: '/settings/account',\n name: 'Settings Account',\n component: SettingsAccount,\n },\n {\n path: '/settings/jikkyo',\n name: 'Settings Jikkyo',\n component: SettingsJikkyo,\n },\n {\n path: '/settings/twitter',\n name: 'Settings Twitter',\n component: SettingsTwitter,\n },\n {\n path: '/settings/server',\n name: 'Settings Server',\n component: SettingsServer,\n },\n {\n path: '/login/',\n name: 'Login',\n component: Login,\n },\n {\n path: '/register/',\n name: 'Register',\n component: Register,\n },\n {\n path: '*',\n name: 'NotFound',\n component: NotFound,\n },\n ],\n\n // ページ遷移時のスクロールの挙動の設定\n // ref: https://v3.router.vuejs.org/ja/guide/advanced/scroll-behavior.html\n scrollBehavior (to, from, savedPosition) {\n if (savedPosition) {\n // 戻る/進むボタンが押されたときは保存されたスクロール位置を使う\n return savedPosition;\n } else {\n // それ以外は常に先頭にスクロールする\n return {x: 0, y: 0};\n }\n }\n});\n\nexport default router;\n","/* eslint-disable no-console */\n\nimport { register } from 'register-service-worker';\n\nimport Message from '@/message';\nimport Utils from '@/utils';\n\n\nif (process.env.NODE_ENV === 'production') {\n register(`${process.env.BASE_URL}service-worker.js`, {\n ready() {\n console.log(\n 'App is being served from cache by a service worker.\\n' +\n 'For more details, visit https://goo.gl/AFskqB'\n );\n },\n registered() {\n console.log('Service worker has been registered.');\n },\n cached() {\n console.log('Content has been cached for offline use.');\n },\n updatefound() {\n console.log('New content is downloading.');\n },\n updated(registration: ServiceWorkerRegistration) {\n console.log('New content is available; please refresh.');\n Message.show({\n message: 'クライアントが新しいバージョンに更新されました。5秒後にリロードします。',\n timeout: 10000, // リロードするまで表示し続ける\n });\n // PWA (Service Worker) を更新する\n registration.waiting.postMessage({type: 'SKIP_WAITING'});\n registration.waiting.addEventListener('statechange', async (event) => {\n if ((event.target as ServiceWorker).state === 'activated') {\n await Utils.sleep(4); // activated になるまで少し時間がかかるので、1秒減らして4秒待つ\n location.reload(true);\n }\n });\n },\n offline() {\n console.log('No internet connection found. App is running in offline mode.');\n },\n error(error) {\n console.error('Error during service worker registration:', error);\n }\n });\n}\n","\nimport { Icon } from '@iconify/vue2';\nimport { createPinia, PiniaVuePlugin } from 'pinia';\nimport { polyfill as SeamlessScrollPolyfill } from 'seamless-scroll-polyfill';\nimport VTooltip from 'v-tooltip';\nimport Vue from 'vue';\nimport VueVirtualScroller from 'vue-virtual-scroller';\nimport 'vue-virtual-scroller/dist/vue-virtual-scroller.css';\nimport VuetifyMessageSnackbar from 'vuetify-message-snackbar';\nimport 'v-tooltip/dist/v-tooltip.css';\n\nimport App from '@/App.vue';\nimport VTabItem from '@/components/Vuetify/VTabItem';\nimport VTabs from '@/components/Vuetify/VTabs';\nimport VTabsItems from '@/components/Vuetify/VTabsItems';\nimport vuetify from '@/plugins/vuetify';\nimport router from '@/router';\nimport useSettingsStore, { setLocalStorageSettings } from '@/store/SettingsStore';\nimport '@/service-worker';\nimport Utils from '@/utils';\n\n\n// スムーズスクロール周りの API の polyfill を適用\n// Element.scrollInfoView() のオプション指定を使うために必要\nSeamlessScrollPolyfill();\n\n// Production Tip を非表示にする\nVue.config.productionTip = false;\n\n// 常に Vue.js devtools を有効にする\nVue.config.devtools = true;\n\n// Pinia を使う\n// ref: https://pinia.vuejs.org/cookbook/options-api.html\nVue.use(PiniaVuePlugin);\nconst pinia = createPinia();\n\n// vue-virtual-scroller を使う\nVue.use(VueVirtualScroller);\n\n// vuetify-message-snackbar を使う\n// マイナーな OSS(しかも中国語…)だけど、Snackbar を関数で呼びたかったのでちょうどよかった\n// ref: https://github.com/thinkupp/vuetify-message-snackbar\nVue.use(VuetifyMessageSnackbar, {\n // 画面上に配置しない\n top: false,\n // 画面下に配置する\n bottom: true,\n // デフォルトの背景色\n color: '#433532',\n // ダークテーマを適用する\n dark: true,\n // 影 (Elevation) の設定\n elevation: 8,\n // 2.5秒でタイムアウト\n timeout: 2500,\n // 要素が非表示になった後に DOM から要素を削除する\n autoRemove: true,\n // 閉じるボタンのテキスト\n closeButtonContent: '閉じる',\n // Vuetify のインスタンス\n vuetifyInstance: vuetify,\n});\n\n// VTooltip を使う\n// タッチデバイスでは無効化する\n// ref: https://v-tooltip.netlify.app/guide/config.html#default-values\nconst trigger = Utils.isTouchDevice() ? [] : ['hover', 'focus', 'touch'];\nVTooltip.options.themes.tooltip.showTriggers = trigger;\nVTooltip.options.themes.tooltip.hideTriggers = trigger;\nVTooltip.options.themes.tooltip.delay.show = 0;\nVTooltip.options.offset = [0, 7];\nVue.use(VTooltip);\n\n// Iconify(アイコン)のグローバルコンポーネント\nVue.component('Icon', Icon);\n\n// VTabItem / VTabs / VTabsItems の挙動を改善するグローバルコンポーネント\nVue.component('v-tab-item-fix', VTabItem);\nVue.component('v-tabs-fix', VTabs);\nVue.component('v-tabs-items-fix', VTabsItems);\n\n// Vue を初期化\n(window as any).KonomiTVVueInstance = new Vue({\n pinia,\n router,\n vuetify,\n render: h => h(App),\n}).$mount('#app');\n\n// 設定データをサーバーにアップロード中かどうか\nlet is_uploading_settings = false;\n\n// 設定データの変更を監視する\nconst settings_store = useSettingsStore();\nsettings_store.$subscribe(async () => {\n\n // 設定データをアップロード中の場合は何もしない\n if (is_uploading_settings === true) {\n return;\n }\n\n // 設定データを LocalStorage に保存\n console.log('Client Settings Changed:', settings_store.settings);\n setLocalStorageSettings(settings_store.settings);\n\n // 設定データをサーバーに同期する (ログイン時かつ同期が有効な場合のみ)\n await settings_store.syncClientSettingsToServer();\n\n}, {detached: true});\n\n// ログイン時かつ設定の同期が有効な場合、ページ遷移に関わらず、常に3秒おきにサーバーから設定を取得する\n// 初回のページレンダリングに間に合わないのは想定内(同期の完了を待つこともできるが、それだと表示速度が遅くなるのでしょうがない)\nwindow.setInterval(async () => {\n if (Utils.getAccessToken() !== null && settings_store.settings.sync_settings === true) {\n\n // 設定データをサーバーにアップロード\n is_uploading_settings = true;\n await settings_store.syncClientSettingsFromServer();\n is_uploading_settings = false;\n\n // 設定データを LocalStorage に保存\n setLocalStorageSettings(settings_store.settings);\n }\n}, 3 * 1000); // 3秒おき\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","var deferred = [];\n__webpack_require__.O = function(result, chunkIds, fn, priority) {\n\tif(chunkIds) {\n\t\tpriority = priority || 0;\n\t\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar chunkIds = deferred[i][0];\n\t\tvar fn = deferred[i][1];\n\t\tvar priority = deferred[i][2];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every(function(key) { return __webpack_require__.O[key](chunkIds[j]); })) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = function(module) {\n\tvar getter = module && module.__esModule ?\n\t\tfunction() { return module['default']; } :\n\t\tfunction() { return module; };\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// define __esModule on exports\n__webpack_require__.r = function(exports) {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t143: 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = function(chunkId) { return installedChunks[chunkId] === 0; };\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = function(parentChunkLoadingFunction, data) {\n\tvar chunkIds = data[0];\n\tvar moreModules = data[1];\n\tvar runtime = data[2];\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some(function(id) { return installedChunks[id] !== 0; })) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunkKonomiTV\"] = self[\"webpackChunkKonomiTV\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","// startup\n// Load entry module and return exports\n// This entry module depends on other loaded chunks and execution need to be delayed\nvar __webpack_exports__ = __webpack_require__.O(undefined, [998], function() { return __webpack_require__(370); })\n__webpack_exports__ = __webpack_require__.O(__webpack_exports__);\n"],"names":["_c","_self","attrs","staticRenderFns","script","component","render","VTabItem","h","props","name","this","computedTransition","on","beforeEnter","onBeforeTransition","afterEnter","onAfterTransition","enterCancelled","onTransitionCancelled","beforeLeave","afterLeave","leaveCancelled","enter","onEnter","genWindowItem","VTabsBar","data","items","methods","register","item","activeItem","internalIndex","push","sort","a","b","index_a","$slots","default","findIndex","element","$vnode","key","index_b","$on","onClick","mandatory","selectedValues","length","updateMandatory","updateItem","indexOf","undefined","updateInternalValue","unregister","constructor","super","options","call","VTabs","genBar","slider","style","height","convertToUnit","activeClass","centerActive","dark","light","optional","mobileBreakpoint","nextIcon","prevIcon","showArrows","value","internalValue","callSlider","change","val","ref","setTextColor","computedColor","setBackgroundColor","backgroundColor","$createElement","genSlider","VTabsItems","updateReverse","oldVal","itemsLength","lastIndex","continuous","Vue","Vuetify","VSnackbar","VBtn","VIcon","theme","themes","primary","secondary","twitter","base","lighten1","lighten2","gray","black","background","lighten3","text","darken1","darken2","darken3","customProperties","Utils","static","localStorage","getItem","access_token","setItem","removeItem","test","navigator","userAgent","blob","filename","blob_url","URL","createObjectURL","link","document","createElement","download","href","click","revokeObjectURL","content","html_escape_table","replace","match","popupSizeWidth","popupSizeHeight","window","screen","posTop","posLeft","width","class_name","activeElement","classList","contains","matchMedia","matches","Math","floor","seconds","Promise","resolve","setTimeout","Date","now","Object","prototype","toString","slice","toLowerCase","escapeHTML","pattern","version","process","api_base_url","location","protocol","host","ChannelUtils","channel_id","result","groups","channel_type","toUpperCase","e","channel_force","success","message","_a","KonomiTVVueInstance","$message","info","warning","error","show","axios_instance","axios","interceptors","request","use","config","baseURL","url","startsWith","headers","timeout","Users","user_create_request","response","APIClient","Message","username","password","URLSearchParams","responseType","user_update_request","icon","form_data","FormData","append","useUserStore","defineStore","state","is_logged_in","user","user_icon_url","getters","user_niconico_icon_url","niconico_user_id","user_id_slice","actions","async","console","log","login","silent","logout","fetchUser","settings_store","useSettingsStore","settings","sync_settings","force","catch","AxiosError","status","Error","detail","is_error","NaN","is_success","assign","method","template","user_store","Number","isNaN","Settings","sync_settings_keys","default_settings","pinned_channel_ids","showed_panel_last_time","selected_twitter_account_id","saved_twitter_hashtags","tv_streaming_quality","tv_data_saver_mode","tv_low_latency_mode","panel_display_state","tv_panel_active_tab","tv_channel_selection_requires_alt_key","caption_font","always_border_caption_text","specify_caption_opacity","caption_opacity","tv_show_superimpose","capture_copy_to_clipboard","capture_save_mode","capture_caption_mode","comment_speed_rate","comment_font_size","close_comment_form_after_sending","muted_comment_keywords","muted_niconico_user_ids","mute_vulgar_comments","mute_abusive_discriminatory_prejudiced_comments","mute_big_size_comments","mute_fixed_comments","mute_colored_comments","mute_consecutive_same_characters_comments","fold_panel_after_sending_tweet","reset_hashtag_when_program_switches","auto_add_watching_channel_hashtag","twitter_active_tab","tweet_hashtag_position","tweet_capture_watermark_position","getLocalStorageSettings","JSON","parse","setLocalStorageSettings","stringify","getNormalizedSettings","new_settings","default_settings_key","keys","file","settings_json","syncClientSettingsToServer","default_settings_modified","getSyncableClientSettings","sync_settings_key","settings_server","settings_server_key","settings_server_value","entries","CommentUtils","color","color_table","position","size","comment_mail","commands","split","command","parsed_color","getCommentColor","parsed_position","getCommentPosition","parsed_size","getCommentSize","comment","user_id","includes","special_command_comments_pattern","mute_vulgar_comments_pattern","mute_abusive_discriminatory_prejudiced_comments_pattern","mute_consecutive_same_characters_comments_pattern","muted_comment_keyword","endsWith","RegExp","annoying_statistical_comments_pattern","Buffer","PlayerUtils","background_count","random","padStart","canPlayType","ProgramUtils","program","mark","pattern1","pattern2","dayjs","isSameOrAfter","isSameOrBefore","isBetween","pause_time_start","hour","minute","second","pause_time_end","pause_time_start_23","pause_time_end_23","default_value","progress","diff","start_time","duration","is_short","end_time","format","staticClass","$event","model","callback","expression","_vm","password_showing","directives","rawName","_setup","_v","staticStyle","class","Version","useVersionStore","server_version_info","last_updated_at","client_version","server_version","_b","latest_version","is_client_develop_version","is_server_develop_version","is_update_available","is_version_mismatch","fetchServerVersion","version_info","components","BottomNavigation","computed","mapStores","versionStore","Header","Navigation","is_form_dense","userStore","$router","path","_setupProxy","_m","password_validation","username_validation","$refs","validate","is_admin","overrideServerSettingsFromClient","sync_settings_dialog","preventDefault","settings_username","settings_icon","$$v","settings_password_showing","settings_password","scopedSlots","account_delete_confirm_dialog","SettingsBase","is_loading","settings_username_validation","settings_password_validation","watch","settingsStore","sync_settings_json","server_sync_settings","server_sync_settings_json","syncClientSettingsFromServer","update_type","updateUser","updateUserIcon","deleteUser","$set","isHEVCVideoSupported","import_settings_file","QUALITY_H264","QUALITY_H265","created","immediate","handler","exportSettings","settings_json_blob","Blob","type","importClientSettings","go","resetClientSettings","comment_mute_settings_modal","muted_comment_keyword_match_type","prop","event","showing","Boolean","required","interval_timer_id","$emit","Niconico","authorization_url","CommentMuteSettings","hash","params","get","authorization_status","parseInt","authorization_detail","onOAuthCallbackReceived","history","replaceState","popup_window","open","onMessage","closed","close","removeEventListener","addEventListener","twitter_account","loginTwitterAccountWithPasswordForm","twitter_password_auth_dialog","twitter_screen_name","twitter_password_showing","twitter_password","Twitter","twitter_password_auth_request","screen_name","captures","tweet_capture","is_twitter_password_auth_sending","twitter_form","current_twitter_account","twitter_accounts","updated_at","reset","Array","from","channels_type","channels","id","removePinnedChannel","stopPropagation","domProps","_s","IProgramDefault","network_id","service_id","event_id","title","description","is_free","genre","video_type","video_codec","video_resolution","primary_audio_type","primary_audio_language","primary_audio_sampling_rate","secondary_audio_type","secondary_audio_language","secondary_audio_sampling_rate","IChannelDefault","transport_stream_id","remocon_id","channel_number","channel_name","channel_comment","is_subchannel","is_radiochannel","is_display","viewers","program_present","program_following","Channels","useChannelsStore","channels_list","GR","BS","CS","CATV","SKY","STARDIGIO","is_channels_list_initial_updated","is_showing_live","channel","getChannelType","previous","current","next","current_channel_index","IProgramError","IChannelError","previous_channel_index","index","next_channel_index","channels_list_with_pinned","Map","set","_d","_e","_f","_g","delete","has","channels_list_with_pinned_for_watch","getChannel","find","getChannelByRemoconID","updateChannel","update","tab","interval_ids","residue_second","getSeconds","channelsStore","setInterval","beforeDestroy","interval_id","clearInterval","addPinnedChannel","splice","isPinnedChannel","controlDisplayTimer","modifiers","backgroundImage","is_panel_display","player","shortcut_key_modal","shortcut_key_column_name","shortcut_key_column","shortcut_keys","key_name","_l","getProgramTime","getProgramProgress","is_comment_list_dropdown_display","fn","active","time","isTouchDevice","initialize_failed_message","is_manual_scroll","LiveCommentManager","on_initial_comments_received","on_comment_received","watch_session","comment_session","vpos_base_timestamp","keep_seat_interval_id","abort_controller","AbortController","watch_session_result","initWatchSession","initCommentSession","is_disconnect_message_received","watch_session_info","notice","WebSocket","audience_token","send","signal","code","reconnect","readyState","OPEN","keepIntervalSec","disconnect_reason","reason","vposBaseTime","valueOf","threadId","message_server_url","messageServer","uri","thread_id","your_post_key","yourPostKey","comment_session_info","initial_comments_buffer","initial_comments_received","ping","thread","threadkey","res_from","resultcode","chat","yourpost","parseCommentCommand","mail","isMutedComment","comment_data","no","date","my_post","buffered_end","video","buffered","end","comment_delay_time","currentTime","paused","danmaku","draw","sendComment","position_table","vpos","round","abort","warn","destroy","initSession","is_auto_scrolling","comment_list","comment_list_element","comment_list_dropdown_top","comment_list_dropdown_comment","live_comment_manager","visibilitychange_listener","resize_observer","resize_observer_element","mounted","$el","querySelector","is_user_scrolling","onmousedown","x","clientX","getBoundingClientRect","left","clientWidth","onmouseup","on_user_scrolling","is_dragging","ontouchstart","ontouchend","ontouchmove","onwheel","onscroll","scrollTop","offsetHeight","scrollHeight","unobserve","showCommentListDropdown","comment_list_wrapper_rect","comment_list_wrapper","comment_list_dropdown_height","comment_button_rect","currentTarget","top","hideCommentListDropdown","filter","addMutedKeywords","addMutedNiconicoUserIds","addMutedNiconicoUserIDs","smooth","scrollTo","behavior","initReserveObserver","animation_timeout_id","on_resize","video_element","comment_area_element","clientHeight","letter_box_height","threshold","comment_area_vertical_margin","comment_area_width","comment_area_height","gcd","y","gcd_result","comment_area_height_aspect","transition","setProperty","clearTimeout","removeProperty","ResizeObserver","observe","comment_list_buffer","max_comment_count","initial_comments","scrollCommentList","visibilityState","max","comment_list_and_buffer_length","niconico_user_premium","decorateProgramInfo","getAttribute","genre_index","major","middle","getChannelForceType","detail_text","detail_heading","URLtoLink","zoom_capture_modal","capture","clickCapture","target","tweet_hashtag","is_tweet_hashtag_form_focused","updateTweetLetterCount","tweet_text","is_tweet_text_form_focused","is_logged_in_twitter","tweet_letter_count","editing","hashtag","updateSelectedTwitterAccount","draggable","is_virtual_keyboard_display","selected_twitter_account","is_twitter_account_list_display","map","is_hashtag_list_display","zoom_capture","captures_element","tweet_captures","is_tweet_sending","some","twitter_account_index","formatHashtag","image_url","deep","pasteClipboardData","clipboard_item","clipboardData","getAsFile","addCaptureList","clickHashtagListButton","clickHashtag","clickAccountButton","fullscreenElement","exitFullscreen","selected","shift","focused","$nextTick","image_bitmap","createImageBitmap","canvas","OffscreenCanvas","context","getContext","alpha","desynchronized","willReadFrequently","drawImage","font","fillStyle","shadowColor","shadowBlur","shadowOffsetX","shadowOffsetY","textAlign","textBaseline","fillText","convertToBlob","quality","toBlob","getChannelHashtag","from_hashtag_list","tweet_hashtag_array","trim","replaceAll","channel_hashtag","join","new_tweet_captures","drawProgramTitleOnCapture","then","blur","Captures","web_font_noto_sans_base64","web_font_open_sans_base64","CaptureHandler","captured_callback","player_container","container","insertAdjacentHTML","comment_capture_button","capture_button","canvas_context","videoWidth","videoHeight","web_font_noto_sans_url","web_font_open_sans_url","base64_font_prefix","web_font_noto_sans","web_font_open_sans","with_comments","total_time","channels_store","addHighlight","filename_base","filename_caption","caption_canvas","plugins","aribb24Caption","getRawCanvas","superimpose_canvas","aribb24Superimpose","is_caption_showing","isShowing","isPresent","is_superimpose_showing","caption_text","getTextContent","exif_options","_h","_k","_j","is_caption_composited","is_comment_composited","export_and_save","exportToBlob","setEXIFDataToCapture","capture_normal","capture_caption","promises","comments_image","createCommentsImage","drawComments","filename_real","all","bitmap_canvas","transferFromImageBitmap","removeHighlight","copyBlobToClipboard","convertBlobToPng","add","remove","html","svg","image","Image","src","encodeURIComponent","decode","comments_html","outerHTML","querySelectorAll","commentsHTMLtoSVGImage","offsetWidth","draw_scale_ratio","draw_height","HTMLCanvasElement","reject","captured_playback_position","json","captured_at","datetime","exif","piexif","TagValues","ImageIFD","XResolution","YResolution","ResolutionUnit","YCbCrPositioning","DateTime","Software","XPComment","ExifIFD","ExifVersion","ComponentsConfiguration","FlashpixVersion","ColorSpace","DateTimeOriginal","DateTimeDigitized","exif_string","dump","blob_string","reader","FileReader","onload","onerror","readAsBinaryString","blob_string_new","insert","buffer","Uint8Array","charCodeAt","PLAYBACK_BUFFER_SEC_LOW_LATENCY","PLAYBACK_BUFFER_SEC","Channel","Comment","Program","background_url","is_video_buffering","is_background_display","is_control_display","is_fullscreen","is_ime_composing","is_comment_send_just_did","control_interval_id","is_zapping","is_zapping_continuously","player_can_be_destroyed","is_mpegts_supported","mpegts","romsounds_context","romsounds_buffers","eventsource","fullscreen_handler","capture_handler","shortcut_key_handler","shortcut_key_pressed_at","shortcut_key_list","left_column","icon_height","shortcuts","right_column","unshift","$route","virtualKeyboard","overlaysContent","ongeometrychange","boundingRect","init","AudioContext","audio_data","decodeAudioData","beforeRouteUpdate","to","destroy_promise","new_channel","old_channel","twitter_component","old_channel_hashtag","generatePlayerBackgroundURL","initPlayer","initEventHandler","initCaptureHandler","initShortcutKeyHandler","audioItem","liveLLHLSForKonomiTV","audioValue","textContent","tran","switchPrimaryAudio","artwork","sizes","mediaSession","metadata","MediaMetadata","artist","setPositionState","playbackRate","setActionHandler","play","pause","is_player_event","is_touch_device","controller","hide","setting","isShow","playback_buffer_sec","DPlayer","lang","live","liveSyncMinBufferSize","loop","airplay","autoplay","hotkey","screenshot","volume","defaultQuality","qualities","hevc_prefix","speedRate","fontSize","apiBackend","read","pluginOptions","enableWorker","enableStashBuffer","liveSync","liveSyncMaxLatency","liveSyncTargetLatency","liveSyncPlaybackRate","aribb24","normalFont","forceStrokeColor","forceBackgroundColor","opacity","drcsReplacement","enableRawCanvas","useStroke","usePUA","PRACallback","resume","buffer_source_node","createBufferSource","gain_node","createGain","connect","destination","gain","start","subtitle","setAutoHide","commentInput","settingOriginPanel","settingOriginPanelHeight","settingBox","clipPath","fullscreen_container","fullScreen","isFullScreen","onfullscreenchange","webkitFullscreenElement","cancel","requestFullscreen","webkitRequestFullscreen","orientation","lock","webkitExitFullscreen","unlock","on_play_or_pause","sync","crossOrigin","error_type","reload","force_play","pause_icon","playButton","innerHTML","mobilePlayButton","on_canplay","oncanplay","oncanplaythrough","get_playback_buffer_sec","current_playback_buffer_sec","recover","current_volume","eventsource_url","EventSource","event_raw","clients_count","load","pictureInPictureElement","exitPictureInPicture","requestPictureInPicture","switchVideo","clear","tweet_form_element","tag","tagName","editable","is_repeat","repeat","focus","scrollLeft","ctrlKey","metaKey","shiftKey","altKey","switch_channel_type","switch_remocon_id","switch_channel","focused_capture_index","focused_capture","focused_capture_element","parentElement","scrollIntoView","block","inline","toggle","pictureInPictureEnabled","pipButton","showDanmaku","showDanmakuToggle","checked","captureAndSave","is_destroy_player","i","VueRouter","router","mode","routes","redirect","TVHome","TVWatch","SettingsIndex","SettingsGeneral","SettingsCaption","SettingsCapture","SettingsAccount","SettingsJikkyo","SettingsTwitter","SettingsServer","Login","Register","NotFound","scrollBehavior","savedPosition","ready","registered","cached","updatefound","updated","registration","waiting","postMessage","offline","SeamlessScrollPolyfill","PiniaVuePlugin","pinia","createPinia","VueVirtualScroller","VuetifyMessageSnackbar","bottom","elevation","autoRemove","closeButtonContent","vuetifyInstance","vuetify","trigger","VTooltip","Icon","App","$mount","is_uploading_settings","$subscribe","detached","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","exports","module","__webpack_modules__","m","deferred","O","chunkIds","priority","notFulfilled","Infinity","fulfilled","j","every","r","n","getter","__esModule","d","definition","o","defineProperty","enumerable","g","globalThis","Function","obj","hasOwnProperty","Symbol","toStringTag","installedChunks","chunkId","webpackJsonpCallback","parentChunkLoadingFunction","moreModules","runtime","chunkLoadingGlobal","self","forEach","bind","__webpack_exports__"],"sourceRoot":""} \ No newline at end of file diff --git a/client/dist/assets/js/app.8188c78a.js b/client/dist/assets/js/app.8188c78a.js deleted file mode 100644 index 883034f3..00000000 --- a/client/dist/assets/js/app.8188c78a.js +++ /dev/null @@ -1,2 +0,0 @@ -(function(){"use strict";var t={9537:function(t,e,s){var i=s(2856),a=s(6086),n=s(2803),r=s(7738),o=s(144),l=s(4801),c=s(1797),_=s.n(c),d=s(1096),u=function(){var t=this,e=t._self._c;return e(d.Z,{attrs:{id:"app"}},[e("transition",[e("router-view")],1)],1)},m=[],h=s(1001),p={},g=(0,h.Z)(p,u,m,!1,null,null,null),v=g.exports,f=s(6878),w=f.Z.extend({render(t){return t("transition",{props:{name:this.computedTransition},on:{beforeEnter:this.onBeforeTransition,afterEnter:this.onAfterTransition,enterCancelled:this.onTransitionCancelled,beforeLeave:this.onBeforeTransition,afterLeave:this.onAfterTransition,leaveCancelled:this.onTransitionCancelled,enter:this.onEnter}},[this.genWindowItem()])}}),y=s(6998),b=s(1050),C=(s(7658),s(1361)),k=C.Z.extend({data(){return{items:[]}},methods:{register(t){const e=this.items[this.internalIndex];this.items.push(t),this.items.sort(((t,e)=>{const s=this.$slots.default.findIndex((e=>t.$vnode.key===e.key)),i=this.$slots.default.findIndex((t=>e.$vnode.key===t.key));return s-i})),t.$on("change",(()=>this.onClick(t))),this.mandatory&&!this.selectedValues.length&&this.updateMandatory(),this.updateItem(t,this.items.indexOf(t)),void 0!==e&&this.updateInternalValue(this.items.indexOf(e))},unregister(t){const e=this.items[this.internalIndex];this.constructor.super.options.methods.unregister.call(this,t),void 0!==e&&this.updateInternalValue(this.items.indexOf(e))}}}),x=y.Z.extend({methods:{genBar(t,e){const s={style:{height:(0,b.kb)(this.height)},props:{activeClass:this.activeClass,centerActive:this.centerActive,dark:this.dark,light:this.light,mandatory:!this.optional,mobileBreakpoint:this.mobileBreakpoint,nextIcon:this.nextIcon,prevIcon:this.prevIcon,showArrows:this.showArrows,value:this.internalValue},on:{"call:slider":this.callSlider,change:t=>{this.internalValue=t}},ref:"items"};return this.setTextColor(this.computedColor,s),this.setBackgroundColor(this.backgroundColor,s),this.$createElement(k,s,[this.genSlider(e),t])}}}),S=s(6052),O=S.Z.extend({data(){return{items:[]}},methods:{register(t){const e=this.items[this.internalIndex];this.items.push(t),this.items.sort(((t,e)=>{const s=this.$slots.default.findIndex((e=>t.$vnode.key===e.key)),i=this.$slots.default.findIndex((t=>e.$vnode.key===t.key));return s-i})),t.$on("change",(()=>this.onClick(t))),this.mandatory&&!this.selectedValues.length&&this.updateMandatory(),this.updateItem(t,this.items.indexOf(t)),void 0!==e&&this.items.indexOf(e)!==this.internalValue&&this.updateInternalValue(this.items.indexOf(e))},unregister(t){const e=this.items[this.internalIndex];this.constructor.super.options.methods.unregister.call(this,t),void 0!==e&&this.updateInternalValue(this.items.indexOf(e))},updateReverse(t,e){const s=this.items.length,i=s-1;return s<=2?t":">"};return t.replace(/[&"'<>]/g,(t=>e[t]))}static getWindowFeatures(){const t=650,e=window.screen.height>=800?800:window.screen.height-100,s=(window.screen.height-e)/2,i=(window.screen.width-t)/2;return`toolbar=0,status=0,top=${s},left=${i},width=${t},height=${e},modal=yes,alwaysRaised=yes`}static hasActiveElementClass(t){return null!==document.activeElement&&document.activeElement.classList.contains(t)}static isFirefox(){return/Firefox/i.test(navigator.userAgent)}static isMobileDevice(){return/iPhone|iPad|iPod|Macintosh|Android|Mobile/i.test(navigator.userAgent)&&"ontouchend"in document}static isSmartphoneHorizontal(){return window.matchMedia("(max-width: 1000px) and (max-height: 450px)").matches}static isSmartphoneVertical(){return window.matchMedia("(max-width: 600px) and (min-height: 450.01px)").matches}static isTabletHorizontal(){return window.matchMedia("(max-width: 1264px) and (max-height: 850px)").matches}static isTabletVertical(){return window.matchMedia("(max-width: 850px) and (min-height: 850.01px)").matches}static isTouchDevice(){return window.matchMedia("(hover: none)").matches}static mathFloor(t,e=0){return Math.floor(t*10**e)/10**e}static async sleep(t){return await new Promise((e=>setTimeout(e,1e3*t)))}static time(){return Date.now()/1e3}static typeof(t){return Object.prototype.toString.call(t).slice(8,-1).toLowerCase()}static URLtoLink(t){t=$.escapeHTML(t);const e=/(https?:\/\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/gi;return t.replace(e,'$1')}}$.version="0.7.0",$.api_base_url=(()=>`${window.location.protocol}//${window.location.host}/api`)();var A=$;class D{static getChannelType(t){try{const e=t.match("(?[a-z]+)[0-9]+").groups.channel_type.toUpperCase();return e}catch(e){return"GR"}}static getChannelForceType(t){return null===t?"normal":t>=500?"festival":t>=200?"so-many":t>=100?"many":"normal"}}var B=s(8764),N=s(9204),K={success(t){var e;return null===(e=window.KonomiTVVueInstance)||void 0===e?void 0:e.$message.success(t)},info(t){var e;return null===(e=window.KonomiTVVueInstance)||void 0===e?void 0:e.$message.info(t)},warning(t){var e;return null===(e=window.KonomiTVVueInstance)||void 0===e?void 0:e.$message.warning(t)},error(t){var e;return null===(e=window.KonomiTVVueInstance)||void 0===e?void 0:e.$message.error(t)},show(t){var e;return null===(e=window.KonomiTVVueInstance)||void 0===e?void 0:e.$message.show(t)}},L=s(6154);const E=L["default"].create();E.interceptors.request.use((t=>{var e;if(void 0===t.baseURL&&(t.baseURL=ht.api_base_url),!1===(null===(e=t.url)||void 0===e?void 0:e.startsWith("http"))){const e=ht.getAccessToken();null!==e&&(t.headers["Authorization"]=`Bearer ${e}`),t.headers["X-KonomiTV-Version"]=ht.version}return t.timeout=3e4,t}));var H=E;class M{static async createUser(t){const e=await G.post("/users",t);if("is_error"in e){switch(e.error.message){case"Specified username is duplicated":K.error("ユーザー名が重複しています。");break;case"Specified username is not accepted due to system limitations":K.error("ユーザー名に token と me は使えません。");break;default:G.showGenericError(e,"アカウントを作成できませんでした。");break}return null}return e.data}static async createUserAccessToken(t,e){const s=await G.post("/users/token",new URLSearchParams({username:t,password:e}));if("is_error"in s){switch(s.error.message){case"Incorrect username":K.error("ログインできませんでした。そのユーザー名のアカウントは存在しません。");break;case"Incorrect password":K.error("ログインできませんでした。パスワードを間違えていませんか?");break;default:G.showGenericError(s,"ログインできませんでした。");break}return null}return s.data}static async fetchUser(){const t=await G.get("/users/me");return"is_error"in t?(G.showGenericError(t,"アカウント情報を取得できませんでした。"),null):t.data}static async fetchUserIcon(){const t=await G.get("/users/me/icon",{responseType:"blob"});return"is_error"in t?(G.showGenericError(t,"アイコン画像を取得できませんでした。"),null):URL.createObjectURL(t.data)}static async updateUser(t){const e=await G.put("/users/me",t);if("is_error"in e)switch(e.error.message){case"Specified username is duplicated":K.error("ユーザー名が重複しています。");break;case"Specified username is not accepted due to system limitations":K.error("ユーザー名に token と me は使えません。");break;default:G.showGenericError(e,"アカウント情報を更新できませんでした。");break}else;}static async updateUserIcon(t){const e=new FormData;e.append("image",t);const s=await G.put("/users/me/icon",e,{headers:{"Content-Type":"multipart/form-data"}});if("is_error"in s)switch(s.error.message){case"Please upload JPEG or PNG image":K.error("JPEG または PNG 画像をアップロードしてください。");break;default:G.showGenericError(s,"アイコン画像を更新できませんでした。");break}else;}static async deleteUser(){const t=await G["delete"]("/users/me");"is_error"in t&&G.showGenericError(t,"アカウントを削除できませんでした。")}}var U=M;const V=(0,a.Q_)("user",{state:()=>({is_logged_in:!1,user:null,user_icon_url:null}),getters:{user_niconico_icon_url(){if(null===this.user||null===this.user.niconico_user_id)return null;const t=this.user.niconico_user_id.toString().slice(0,4);return`https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/${t}/${this.user.niconico_user_id}.jpg`}},actions:{async register(t,e){const s=await U.createUser({username:t,password:e});return null===s?(console.log("Register failed."),!1):(await this.login(t,e,!0),console.log("Register successful."),K.success("アカウントを作成しました。"),!0)},async login(t,e,s=!1){const i=await U.createUserAccessToken(t,e);return null===i?(console.log("Login failed."),this.logout(!0),!1):(console.log("Login successful."),ht.saveAccessToken(i.access_token),this.is_logged_in=!0,await this.fetchUser(!0),!1===s&&K.success("ログインしました。"),!0)},logout(t=!1){const e=st();e.settings.sync_settings=!1,ht.deleteAccessToken(),this.is_logged_in=!1,this.user=null,this.user_icon_url="",!1===t&&K.success("ログアウトしました。")},async fetchUser(t=!1){if(null===ht.getAccessToken())return null;if(null!==this.user&&!1===t)return this.user;const e=await U.fetchUser();if(null===e)return null===ht.getAccessToken()&&this.logout(!0),null;this.is_logged_in=!0,this.user=e;const s=await U.fetchUserIcon();return null===s?null:(this.user_icon_url=s,this.user)},async updateUser(t){await U.updateUser(t),await this.fetchUser(!0),void 0!==t.username?K.show("ユーザー名を更新しました。"):void 0!==t.password&&K.show("パスワードを更新しました。")},async updateUserIcon(t){await U.updateUserIcon(t),await this.fetchUser(!0),K.show("アイコン画像を更新しました。")},async deleteUser(){await U.deleteUser(),this.logout(!0),K.show("アカウントを削除しました。")}}});var R=V;class F{static async request(t){const e=await H.request(t).catch((t=>t));return e instanceof N.d7?(console.error(e),e.response?{status:e.response.status,data:null,error:new Error(e.response.data.detail),is_error:!0}:{status:NaN,data:null,error:e,is_error:!0}):{status:e.status,data:e.data,error:null,is_success:!0}}static async get(t,e){const s=Object.assign({url:t,method:"GET"},e);return await F.request(s)}static async post(t,e,s){const i=Object.assign({url:t,method:"POST",data:e},s);return await F.request(i)}static async put(t,e,s){const i=Object.assign({url:t,method:"PUT",data:e},s);return await F.request(i)}static async delete(t,e){const s=Object.assign({url:t,method:"DELETE"},e);return await F.request(s)}static showGenericError(t,e){const s=R();switch(t.error.message){case"Not authenticated":return s.logout(!0),void K.error(`${e}\nログインし直してください。`);case"Access token data is invalid":return s.logout(!0),void K.error(`${e}\nログインセッションが不正です。もう一度ログインし直してください。`);case"Access token is invalid":return s.logout(!0),void K.error(`${e}\nログインセッションの有効期限が切れています。もう一度ログインし直してください。`);case"User associated with access token does not exist":return s.logout(!0),void K.error(`${e}\nログインセッションに紐づくユーザーが存在しないか、削除されています。`);default:return void(t.error.message?Number.isNaN(t.status)?K.error(`${e}(${t.error.message})`):K.error(`${e}(HTTP Error ${t.status} / ${t.error.message})`):K.error(`${e}(HTTP Error ${t.status})`))}}}var G=F;class W{static async fetchClientSettings(){const t=await G.get("/settings/client");return"is_error"in t?null:t.data}static async updateClientSettings(t){await G.put("/settings/client",t)}}var q=W;const X=["pinned_channel_ids","saved_twitter_hashtags","panel_display_state","tv_panel_active_tab","tv_channel_selection_requires_alt_key","caption_font","always_border_caption_text","specify_caption_opacity","caption_opacity","tv_show_superimpose","capture_save_mode","capture_caption_mode","comment_speed_rate","comment_font_size","close_comment_form_after_sending","muted_comment_keywords","muted_niconico_user_ids","mute_vulgar_comments","mute_abusive_discriminatory_prejudiced_comments","mute_big_size_comments","mute_fixed_comments","mute_colored_comments","mute_consecutive_same_characters_comments","fold_panel_after_sending_tweet","reset_hashtag_when_program_switches","auto_add_watching_channel_hashtag","twitter_active_tab","tweet_hashtag_position","tweet_capture_watermark_position"],Y={pinned_channel_ids:[],showed_panel_last_time:!0,selected_twitter_account_id:null,saved_twitter_hashtags:[],tv_streaming_quality:"1080p",tv_data_saver_mode:!1,tv_low_latency_mode:!0,panel_display_state:"RestorePreviousState",tv_panel_active_tab:"Program",tv_channel_selection_requires_alt_key:!1,caption_font:"Windows TV MaruGothic",always_border_caption_text:!0,specify_caption_opacity:!1,caption_opacity:.5,tv_show_superimpose:!0,capture_copy_to_clipboard:!1,capture_save_mode:"UploadServer",capture_caption_mode:"Both",sync_settings:!1,comment_speed_rate:1,comment_font_size:34,close_comment_form_after_sending:!0,muted_comment_keywords:[],muted_niconico_user_ids:[],mute_vulgar_comments:!0,mute_abusive_discriminatory_prejudiced_comments:!0,mute_big_size_comments:!0,mute_fixed_comments:!1,mute_colored_comments:!1,mute_consecutive_same_characters_comments:!1,fold_panel_after_sending_tweet:!1,reset_hashtag_when_program_switches:!0,auto_add_watching_channel_hashtag:!0,twitter_active_tab:"Capture",tweet_hashtag_position:"Append",tweet_capture_watermark_position:"None"};function J(){const t=localStorage.getItem("KonomiTV-Settings");return null!==t?JSON.parse(t):(Q(Y),Y)}function Q(t){localStorage.setItem("KonomiTV-Settings",JSON.stringify(t))}function tt(t){const e={};for(const s of Object.keys(Y))e[s]=s in t?t[s]:Y[s];return e}const et=(0,a.Q_)("settings",{state:()=>{const t=J(),e=tt(t);return Q(e),{settings:e}},actions:{async importClientSettings(t){const e=await t.text();let s={};try{s=JSON.parse(e)}catch(a){return!1}const i=tt(s);return Q(i),this.settings=i,await this.syncClientSettingsToServer(),!0},async resetClientSettings(){const t=Object.assign(Object.assign({},Y),{sync_settings:this.settings.sync_settings});Q(t),this.settings=t,await this.syncClientSettingsToServer()},getSyncableClientSettings(){const t={};for(const e of X)e in this.settings?t[e]=this.settings[e]:t[e]=Y[e];return t},async syncClientSettingsFromServer(t=!1){if(null===ht.getAccessToken()||!1===this.settings.sync_settings&&!1===t)return;const e=await q.fetchClientSettings();if(null!==e)for(const[s,i]of Object.entries(e))this.settings[s]=i},async syncClientSettingsToServer(t=!1){if(null===ht.getAccessToken()||!1===this.settings.sync_settings&&!1===t)return;const e=this.getSyncableClientSettings();await q.updateClientSettings(e)}}});var st=et;class it{static getCommentColor(t){return this.color_table[t]||null}static getCommentPosition(t){switch(t){case"ue":return"top";case"naka":return"right";case"shita":return"bottom";default:return null}}static getCommentSize(t){switch(t){case"big":case"medium":case"small":return t;default:return null}}static parseCommentCommand(t){let e="#FFEAEA",s="right",i="medium";if(void 0!==t&&null!==t){const a=t.replace("184","").split(" ");for(const t of a){const a=it.getCommentColor(t),n=it.getCommentPosition(t),r=it.getCommentSize(t);null!==a&&(e=a),null!==n&&(s=n),null!==r&&(i=r)}}return{color:e,position:s,size:i}}static isMutedComment(t,e,s,i,a){const n=st();if(n.settings.muted_niconico_user_ids.includes(e))return!0;if(it.special_command_comments_pattern.test(t))return!0;if(!0===n.settings.mute_fixed_comments&&("top"===i||"bottom"===i))return console.log("[CommentUtils] Muted comment (fixed_comments): "+t),!0;if(!0===n.settings.mute_colored_comments&&"#FFEAEA"!==s)return console.log("[CommentUtils] Muted comment (colored_comments): "+t),!0;if(!0===n.settings.mute_big_size_comments&&"big"===a)return console.log("[CommentUtils] Muted comment (big_size_comments): "+t),!0;if(!0===n.settings.mute_vulgar_comments&&it.mute_vulgar_comments_pattern.test(t))return console.log("[CommentUtils] Muted comment (vulgar_comments): "+t),!0;if(!0===n.settings.mute_abusive_discriminatory_prejudiced_comments&&it.mute_abusive_discriminatory_prejudiced_comments_pattern.test(t))return console.log("[CommentUtils] Muted comment (abusive_discriminatory_prejudiced_comments): "+t),!0;if(!0===n.settings.mute_consecutive_same_characters_comments&&it.mute_consecutive_same_characters_comments_pattern.test(t))return console.log("[CommentUtils] Muted comment (consecutive_same_characters_comments): "+t),!0;for(const r of n.settings.muted_comment_keywords)if(""!==r.pattern)switch(r.match){case"partial":if(t.includes(r.pattern))return console.log("[CommentUtils] Muted comment (partial): "+t),!0;break;case"forward":if(t.startsWith(r.pattern))return console.log("[CommentUtils] Muted comment (forward): "+t),!0;break;case"backward":if(t.endsWith(r.pattern))return console.log("[CommentUtils] Muted comment (backward): "+t),!0;break;case"exact":if(t===r.pattern)return console.log("[CommentUtils] Muted comment (exact): "+t),!0;break;case"regex":if(new RegExp(r.pattern).test(t))return console.log("[CommentUtils] Muted comment (regex): "+t),!0;break}return!!it.annoying_statistical_comments_pattern.test(t)}static addMutedKeywords(t){const e=st();for(const s of e.settings.muted_comment_keywords)if("exact"===s.match&&s.pattern===t)return;e.settings.muted_comment_keywords.push({match:"exact",pattern:t})}static addMutedNiconicoUserIDs(t){const e=st();e.settings.muted_niconico_user_ids.includes(t)||e.settings.muted_niconico_user_ids.push(t)}}it.mute_vulgar_comments_pattern=new RegExp(B.lW.from("XChpXCl8XChVXCl8cHJwcnzvvZDvvZLvvZDvvZJ8U0VYfFPjgIdYfFPil69YfFPil4tYfFPil49YfO+8s++8pe+8uHzvvLPjgIfvvLh877yz4pev77y4fO+8s+KXi++8uHzvvLPil4/vvLh844Ki44OA44Or44OIfOOCouODiuOCpXzjgqLjg4rjg6t844Kk44Kr6IetfOOCpOOBj3zjgYbjgpPjgZN844Km44Oz44KzfOOBhuOCk+OBoXzjgqbjg7Pjg4F844Ko44Kt44ObfOOBiOOBoeOBiOOBoXzjgYjjgaPjgaF844Ko44OD44OBfOOBiOOBo+OCjXzjgqjjg4Pjg61844GI44KNfOOCqOODrXzlt6Xlj6N844GK44GV44KP44KK44G+44KTfOOBiuOBl+OBo+OBk3zjgqrjgrfjg4PjgrN844Kq44OD44K144OzfOOBiuOBo+OBseOBhHzjgqrjg4Pjg5HjgqR844Kq44OK44OL44O8fOOBiuOBquOBu3zjgqrjg4rjg5t844GK44Gx44GEfOOCquODkeOCpHzjgYpwfOOBiu+9kHzjgqrjg5Xjg5HjgrN844Ks44Kk44K444OzfOOCreODs+OCv+ODnnzjgY/jgbHjgYJ844GP44Gx44GBfOOCr+ODquODiOODquOCuXzjgq/jg7Pjg4t844GU44GP44GU44GP44GU44GP44GU44GPfOOCs+ODs+ODieODvOODoHzjgZHjgaTjgYLjgap844Kx44OE44Ki44OKfOOCtuODvOODoeODs3zjgrfjgrN844GX44GT44GX44GTfOOCt+OCs+OCt+OCs3zjgZnjgZHjgZnjgZF844Gb44GE44GI44GNfOOBm+OBhOOCinzjgZvjg7zjgop844GZ44GF44GF44GF44GF44GFfOOBmeOBhuOBhuOBhuOBhuOBhnzjgrvjgq/jg63jgrl844K744OD44Kv44K5fOOCu+ODleODrHzjgaHjgaPjgbHjgYR844Gh44Gj44OR44KkfOODgeODg+ODkeOCpHzjgaHjgpPjgZN844Gh44CH44GTfOOBoeKXr+OBk3zjgaHil4vjgZN844Gh4peP44GTfOODgeODs+OCs3zjg4HjgIfjgrN844OB4pev44KzfOODgeKXi+OCs3zjg4Hil4/jgrN844Gh44KT44G9fOOBoeOAh+OBvXzjgaHil6/jgb1844Gh4peL44G9fOOBoeKXj+OBvXzjg4Hjg7Pjg51844OB44CH44OdfOODgeKXr+ODnXzjg4Hil4vjg51844OB4peP44OdfOOBoeOCk+OBoeOCk3zjg4Hjg7Pjg4Hjg7N844Gm44GD44KT44Gm44GD44KTfOODhuOCo+ODs+ODhuOCo+ODs3zjg4bjgqPjg7Pjg51844OH44Kr44GEfOODh+ODquODmOODq3zjgarjgYvjgaDjgZd844Gq44GL44CH44GXfOOBquOBi+KXr+OBl3zjgarjgYvil4vjgZd844Gq44GL4peP44GXfOiEseOBknzjg4zjgYR844OM44GLfOODjOOCq3zjg4zjgY1844OM44KtfOODjOOBj3zjg4zjgq9844OM44GRfOODjOOCsXzjg4zjgZN844OM44KzfOOBseOBhOOCguOBv3zjg5Hjg5HmtLt844G144GG44O7fOOBteOBhuKApnzjgbXjgYV8776M772pfOOBteOBj+OCieOBv3zjgbXjgY/jgonjgpPjgad844G644Gj44GffOOBuuOCjeOBuuOCjXzjg5rjg63jg5rjg618776N776f776b776N776f776bfOODleOCp+ODqXzjgbvjgYbjgZHjgYR844G844Gj44GNfOODneODq+ODjnzjgbzjgo3jgpN844Oc44Ot44OzfO++ju++nu++m+++nXzjgb3jgo3jgop844Od44Ot44OqfO++ju++n+++m+++mHzjg57jg7PjgY3jgaR844Oe44Oz44Kt44OEfOOBvuOCk+OBk3zjgb7jgIfjgZN844G+4pev44GTfOOBvuKXi+OBk3zjgb7il4/jgZN844Oe44Oz44KzfOODnuOAh+OCs3zjg57il6/jgrN844Oe4peL44KzfOODnuKXj+OCs3zjgb7jgpPjgZXjgpN844KC44Gj44GT44KKfOODouODg+OCs+ODqnzjgoLjgb/jgoLjgb9844Oi44Of44Oi44OffOODpOOBo+OBn3zjg6TjgaPjgaZ844Ok44KJfOOChOOCieOBm+OCjXzjg6Tjgop844Ok44KLfOODpOOCjHzjg6Tjgo1844Op44OW44ObfOODr+ODrOODoXzmhJvmtrJ85ZaYfOmZsOaguHzpmbDojI586Zmw5ZSHfOa3q+WkonzpmqDmr5t86Zmw5q+bfOeUo+OCgeOCi3zlpbPjga7lrZDjga7ml6V85rGa44Gj44GV44KTfOWnpnzpqI7kuZfkvY185beo5qC5fOW3qOODgeODs3zlt6jnj4186YeR546JfOaciOe1jHzlvozog4zkvY185a2Q56iufOWtkOS9nOOCinzlsITnsr585L+h6ICFfOeyvua2snzpgI/jgZF85oCn5LqkfOeyvuWtkHzmraPluLjkvY185oCn5b60fOaAp+eahHznlJ/nkIZ85a+45q2i44KBfOe0oOadkHzmirHjgYR85oqx44GLfOaKseOBjXzmirHjgY985oqx44GRfOaKseOBk3zkvZPmtrJ85Lmz6aaWfOaBpeWeonznj43mo5J85Lit44Gg44GXfOS4reWHuuOBl3zlsL985oqc44GEfOaKnOOBkeOBquOBhHzmipzjgZHjgot85oqc44GR44KMfOeKr+ePjXzohqjjgol85YyF6IyOfOWLg+i1t3zmkannvoV86a2U576FfOaPieOBvnzmj4njgb985o+J44KAfOaPieOCgXzmvKvmuZZ844CH772efOKXr++9nnzil4vvvZ584peP772efOOAh+ODg+OCr+OCuXzil6/jg4Pjgq/jgrl84peL44OD44Kv44K5fOKXj+ODg+OCr+OCuQ==","base64").toString()),it.mute_abusive_discriminatory_prejudiced_comments_pattern=new RegExp(B.lW.from("44CCfOOCouODi+ODl+ODrOOBj+OCk3zjgqLjg4vjg5fjg6zlkJt844Ki44K544OafOOCpOOCq+OCjHzjgYTjgb7jgYTjgaF844Kk44Oe44Kk44OBfOOCpOODqeOBpOOBj3zjgqbjgrh844Km44O844OofOOCpuODqHzjgqbjg6jjgq9844Km44OyfOOBjeOCguOBhHzjgq3jg6LjgqR844Kt44Oi44GEfOOCrS/jg6Av44OBfOOCrOOCpOOCuHzvvbbvvp7vvbLvvbzvvp5844Ks44KtfOOCq+OCuXzjgq3jg4Pjgrp844GN44Gh44GM44GEfOOCreODgeOCrOOCpHzjgq3jg6Djg4F844K344OKfOOCueODhuODnnzjgaTjgb7jgonjgap844Gk44G+44KJ44KTfOODgeODp+ODg+ODkeODqnzjg4Hjg6fjg7N85Y2D44On44OzfOOBpOOCk+OBvHzjg4Tjg7Pjg5x844ON44OI44Km44OofOOBq+OBoOOBguOBgnzjg4vjg4B85LqM44OAfO++hu++gO++nnzjg5Hjg7zjg6h844OR44OofOODkeODqOOCr3zjgbbjgaPjgZV844OW44OD44K1fOOBtuOBleOBhHzjg5bjgrXjgqR844G+44Gs44GRfOODoeOCr+ODqXzjg5Djgqt844Og44Kr44Gk44GPfOiNkuOCieOBl3zpurvnlJ/jgrvjg6Hjg7Pjg4h85oWw5a6J5ammfOWus+WFkHzlpJblrZd85aem5Zu9fOmfk+WbvXzpn5PkuK186Z+T5pelfOWfuuWcsOWklnzmsJfni4LjgYR85rCX6YGV44GEfOWIh+OBo+OBn3zliIfjgaPjgaZ85rCX5oyB44Gh5oKqfOWbveS6pOaWree1tnzmrrp86aCDfOmgg+OBl3zpoIPjgZl86aCD44GbfOWcqOaXpXzlj4LmlL/mqKl85q2744GtfOawj+OBrXzvvoDvvot85q255YyVfOatueODknzlpLHpgJ986Zqc5a6zfOaWreS6pHzkuK3pn5N85pyd6a6ufOW+tOeUqOW3pXzlo7p85aO3fOWjvHzml6Xpn5N85pel5bidfOeymOedgHzlj43ml6V86aas6bm/fOeZuueLgnznmbrpgZR85py0fOWjsuWbvXzkuI3lv6t85L215ZCIfOmWk+aKnOOBkXzmloflj6V86Z2W5Zu9","base64").toString()),it.mute_consecutive_same_characters_comments_pattern=/(.)\1{7,}/,it.special_command_comments_pattern=/\/[a-z]+ /,it.annoying_statistical_comments_pattern=/最高\d+米\/|計\d+ID|総\d+米/,it.color_table={white:"#FFEAEA",red:"#F02840",pink:"#FD7E80",orange:"#FDA708",yellow:"#FFE133",green:"#64DD17",cyan:"#00D4F5",blue:"#4763FF",purple:"#D500F9",black:"#1E1310",white2:"#CCCC99",niconicowhite:"#CCCC99",red2:"#CC0033",truered:"#CC0033",pink2:"#FF33CC",orange2:"#FF6600",passionorange:"#FF6600",yellow2:"#999900",madyellow:"#999900",green2:"#00CC66",elementalgreen:"#00CC66",cyan2:"#00CCCC",blue2:"#3399FF",marineblue:"#3399FF",purple2:"#6633CC",nobleviolet:"#6633CC",black2:"#666666"};class at{static generatePlayerBackgroundURL(){const t=50,e=Math.floor(Math.random()*t)+1;return`/assets/images/player-backgrounds/${e.toString().padStart(2,"0")}.jpg`}static isHEVCVideoSupported(){return"probably"===document.createElement("video").canPlayType('video/mp4; codecs="hvc1.1.6.L123.B0"')}}var nt=s(7484),rt=s.n(nt),ot=(s(6831),s(6607)),lt=s.n(ot),ct=s(9212),_t=s.n(ct),dt=s(7412),ut=s.n(dt);class mt{static decorateProgramInfo(t,e){if(null!==t&&null!==t[e]){const s=ht.escapeHTML(t[e]),i="新|終|再|交|映|手|声|多|副|字|文|CC|OP|二|S|B|SS|無|無料C|S1|S2|S3|MV|双|デ|D|N|W|P|H|HV|SD|天|解|料|前|後初|生|販|吹|PPV|演|移|他|収|・|英|韓|中|字/日|字/日英|3D|2K|4K|8K|5.1|7.1|22.2|60P|120P|d|HC|HDR|SHV|UHD|VOD|配|初",a=new RegExp("\\((二|字|再)\\)","g"),n=new RegExp(`\\[(${i})\\]`,"g");return s.replace(a,'$1').replace(n,'$1')}{rt().extend(_t()),rt().extend(ut()),rt().extend(lt());const t=rt()(),s=rt()().hour(0).minute(0).second(0),i=rt()().hour(6).minute(59).second(59),a=rt()().hour(23).minute(0).second(0),n=rt()().hour(23).minute(59).second(59);return t.isSameOrAfter(s)&&t.isSameOrBefore(i)||t.isSameOrAfter(a)&&t.isSameOrBefore(n)?"title"===e?"放送休止":"この時間は放送を休止しています。":"title"===e?"番組情報がありません":"この時間の番組情報を取得できませんでした。"}}static getAttribute(t,e,s){return null!==t&&void 0!==t[e]&&null!==t[e]?t[e]:s}static getProgramProgress(t){if(null!==t){const e=rt()(rt()()).diff(t.start_time,"second");return e/t.duration*100}return 0}static getProgramTime(t,e=!1){if(null!==t&&"2000-01-01T00:00:00+09:00"!==t.start_time){rt().locale("ja");const s=rt()(t.start_time),i=rt()(t.end_time),a=t.duration/60;return!0===e?`${s.format("HH:mm")} ~ ${i.format("HH:mm")}`:`${s.format("YYYY/MM/DD (dd) HH:mm")} ~ ${i.format("HH:mm")} (${a}分)`}return!0===e?"--:-- ~ --:--":"----/--/-- (-) --:-- ~ --:-- (--分)"}}var ht=A,pt=s(3058),gt=s(5223),vt=s(4192),ft=s(6904),wt=s(2469),yt=s(5251),bt=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"route-container"},[e("Header"),e("main",[e("Navigation"),e("div",{staticClass:"login-container-wrapper d-flex align-center w-100 mb-13"},[e(pt.Z,{staticClass:"login-container px-10 pt-8 pb-11 mx-auto background lighten-1",attrs:{elevation:"10",width:"100%","max-width":"450"}},[e(gt.EB,{staticClass:"login__logo flex-column justify-center"},[e(wt.Z,{attrs:{"max-width":"250",src:"/assets/images/logo.svg"}}),e("h4",{staticClass:"mt-10"},[t._v("ログイン")])],1),e(vt.Z),e(ft.Z,{ref:"login",on:{submit:function(t){t.preventDefault()}}},[e(yt.Z,{staticClass:"mt-12",attrs:{outlined:"",placeholder:"ユーザー名","hide-details":"",autofocus:"",dense:t.is_form_dense},model:{value:t.username,callback:function(e){t.username=e},expression:"username"}}),e(yt.Z,{staticClass:"mt-8",attrs:{outlined:"",placeholder:"パスワード","hide-details":"",dense:t.is_form_dense,type:t.password_showing?"text":"password","append-icon":t.password_showing?"mdi-eye":"mdi-eye-off"},on:{"click:append":function(e){t.password_showing=!t.password_showing}},model:{value:t.password,callback:function(e){t.password=e},expression:"password"}}),e(j.Z,{staticClass:"login-button mt-5",attrs:{color:"secondary",depressed:"",width:"100%",height:"56"},on:{click:function(e){return t.login()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fa:sign-in"}}),t._v("ログイン ")],1)],1)],1)],1)],1)],1)},Ct=[],kt=s(3347),xt=s(3176),St=function(){var t=this,e=t._self._c;return e("header",{staticClass:"header"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"konomitv-logo ml-3 ml-md-6",attrs:{to:"/tv/"}},[e("img",{staticClass:"konomitv-logo__image",attrs:{src:"/assets/images/logo.svg",height:"21"}})]),e(kt.Z)],1)},Ot=[],Tt={},jt=(0,h.Z)(Tt,St,Ot,!1,null,"84897154",null),It=jt.exports,Pt=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",[e("div",{staticClass:"navigation-container elevation-8"},[e("nav",{staticClass:"navigation"},[e("div",{staticClass:"navigation-scroll"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"navigation__link",attrs:{"active-class":"navigation__link--active",to:"/tv/"}},[e("Icon",{staticClass:"navigation__link-icon",attrs:{icon:"fluent:tv-20-regular",width:"26px"}}),e("span",{staticClass:"navigation__link-text"},[t._v("テレビをみる")])],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"navigation__link",attrs:{"active-class":"navigation__link--active",to:"/videos/"}},[e("Icon",{staticClass:"navigation__link-icon",attrs:{icon:"fluent:movies-and-tv-20-regular",width:"26px"}}),e("span",{staticClass:"navigation__link-text"},[t._v("ビデオをみる")])],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"navigation__link",attrs:{"active-class":"navigation__link--active",to:"/timetable/"}},[e("Icon",{staticClass:"navigation__link-icon",attrs:{icon:"fluent:calendar-ltr-20-regular",width:"26px"}}),e("span",{staticClass:"navigation__link-text"},[t._v("番組表")])],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"navigation__link",attrs:{"active-class":"navigation__link--active",to:"/reserves/"}},[e("Icon",{staticClass:"navigation__link-icon",staticStyle:{padding:"0.5px"},attrs:{icon:"fluent:timer-16-regular",width:"26px"}}),e("span",{staticClass:"navigation__link-text"},[t._v("録画予約")])],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"navigation__link",attrs:{"active-class":"navigation__link--active",to:"/mylist/"}},[e("Icon",{staticClass:"navigation__link-icon",attrs:{icon:"ic:round-playlist-play",width:"26px"}}),e("span",{staticClass:"navigation__link-text"},[t._v("マイリスト")])],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"navigation__link",attrs:{"active-class":"navigation__link--active",to:"/captures/"}},[e("Icon",{staticClass:"navigation__link-icon",attrs:{icon:"fluent:image-multiple-24-regular",width:"26px"}}),e("span",{staticClass:"navigation__link-text"},[t._v("キャプチャ")])],1),e(kt.Z),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"navigation__link",attrs:{"active-class":"navigation__link--active",to:"/settings/"}},[e("Icon",{staticClass:"navigation__link-icon",attrs:{icon:"fluent:settings-20-regular",width:"26px"}}),e("span",{staticClass:"navigation__link-text"},[t._v("設定")])],1),e("a",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.top",value:t.versionStore.is_update_available?`アップデートがあります (version ${t.versionStore.latest_version})`:"",expression:"versionStore.is_update_available ?\n `アップデートがあります (version ${versionStore.latest_version})` : ''",modifiers:{top:!0}}],staticClass:"navigation__link",class:{"navigation__link--version":t.versionStore.is_client_develop_version,"navigation__link--highlight":t.versionStore.is_update_available},attrs:{"active-class":"navigation__link--active",href:"https://github.com/tsukumijima/KonomiTV"}},[e("Icon",{staticClass:"navigation__link-icon",attrs:{icon:"fluent:info-16-regular",width:"26px"}}),e("span",{staticClass:"navigation__link-text"},[t._v("version "+t._s(t.versionStore.client_version))])],1)],1)])]),e("BottomNavigation")],1)},Zt=[],zt=s(1249),$t=function(){var t=this,e=t._self._c;return e(zt.Z,{staticClass:"bottom-navigation-container elevation-12",attrs:{color:"primary",grow:""}},[e(j.Z,{staticClass:"bottom-navigation-button",attrs:{to:"/tv/"}},[e("span",{staticClass:"mt-1"},[t._v("テレビをみる")]),e("Icon",{attrs:{icon:"fluent:tv-20-regular",width:"30px"}})],1),e(j.Z,{staticClass:"bottom-navigation-button",attrs:{to:"/videos/"}},[e("span",{staticClass:"mt-1"},[t._v("ビデオをみる")]),e("Icon",{attrs:{icon:"fluent:movies-and-tv-20-regular",width:"30px"}})],1),e(j.Z,{staticClass:"bottom-navigation-button",attrs:{to:"/settings/"}},[e("span",{staticClass:"mt-1"},[t._v("設定")]),e("Icon",{attrs:{icon:"fluent:settings-20-regular",width:"30px"}})],1)],1)},At=[],Dt={},Bt=(0,h.Z)(Dt,$t,At,!1,null,"3df53df3",null),Nt=Bt.exports;class Kt{static async fetchServerVersion(){const t=await G.get("/version");return"is_error"in t?(G.showGenericError(t,"バージョン情報を取得できませんでした。"),null):t.data}}var Lt=Kt;const Et=(0,a.Q_)("version",{state:()=>({server_version_info:null,last_updated_at:0}),getters:{client_version(){return ht.version},server_version(){var t,e;return null!==(e=null===(t=this.server_version_info)||void 0===t?void 0:t.version)&&void 0!==e?e:null},latest_version(){var t,e;return null!==(e=null===(t=this.server_version_info)||void 0===t?void 0:t.latest_version)&&void 0!==e?e:null},is_client_develop_version(){return this.client_version.includes("-dev")},is_server_develop_version(){var t,e;return null!==(e=null===(t=this.server_version)||void 0===t?void 0:t.includes("-dev"))&&void 0!==e&&e},is_update_available(){return null!==this.server_version&&null!==this.latest_version&&(!1===this.is_server_develop_version&&this.server_version!==this.latest_version||!0===this.is_server_develop_version&&this.server_version.replace("-dev","")===this.latest_version)},is_version_mismatch(){return null!==this.server_version&&this.client_version!==this.server_version}},actions:{async fetchServerVersion(t=!1){if(null!==this.server_version_info&&!1===t)return ht.time()-this.last_updated_at>60&&this.fetchServerVersion(!0),this.server_version_info;const e=await Lt.fetchServerVersion();return null===e?null:(this.server_version_info=e,this.last_updated_at=ht.time(),this.server_version_info)}}});var Ht=Et,Mt=o["default"].extend({name:"Navigation",components:{BottomNavigation:Nt},computed:Object.assign({},(0,a.Kc)(Ht)),async created(){await this.versionStore.fetchServerVersion()}}),Ut=Mt,Vt=(0,h.Z)(Ut,Pt,Zt,!1,null,"5b40940b",null),Rt=Vt.exports,Ft=o["default"].extend({name:"Login",components:{Header:It,Navigation:Rt},data(){return{is_form_dense:ht.isSmartphoneHorizontal(),username:"",password:"",password_showing:!1}},computed:Object.assign({},(0,a.Kc)(R)),async created(){await this.userStore.fetchUser(),this.userStore.is_logged_in&&await this.$router.replace({path:"/settings/account"})},methods:{async login(){if(""===this.username||""===this.password)return void this.$message.error("ユーザー名またはパスワードが空です。");const t=await this.userStore.login(this.username,this.password);!1!==t&&await this.$router.replace({path:"/settings/account"})}}}),Gt=Ft,Wt=(0,h.Z)(Gt,bt,Ct,!1,null,"851c3dec",null),qt=Wt.exports,Xt=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"route-container"},[e("Header"),e("main",[e("Navigation"),t._m(0)],1)],1)},Yt=[function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"d-flex justify-center align-center w-100"},[e("div",{staticClass:"d-flex justify-center align-center flex-column"},[e("h1",[t._v("Not Found, or Under Development...")]),e("span",{staticClass:"mt-4 text--text text--darken-1"},[t._v("お探しのページは存在しないか、鋭意開発中です。")])])])}],Jt=o["default"].extend({name:"NotFound",components:{Header:It,Navigation:Rt}}),Qt=Jt,te=(0,h.Z)(Qt,Xt,Yt,!1,null,"1310cfee",null),ee=te.exports,se=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"route-container"},[e("Header"),e("main",[e("Navigation"),e("div",{staticClass:"register-container-wrapper d-flex align-center w-100 mb-13"},[e(pt.Z,{staticClass:"register-container px-10 pt-8 pb-11 mx-auto background lighten-1",attrs:{elevation:"10",width:"100%","max-width":"450"}},[e(gt.EB,{staticClass:"register__logo flex-column justify-center"},[e(wt.Z,{attrs:{"max-width":"250",src:"/assets/images/logo.svg"}}),e("h4",{staticClass:"mt-10"},[t._v("アカウントを作成")])],1),e(vt.Z),e(ft.Z,{ref:"register",on:{submit:function(t){t.preventDefault()}}},[e(yt.Z,{staticClass:"mt-12",attrs:{outlined:"",placeholder:"ユーザー名",autofocus:"",dense:t.is_form_dense,rules:[t.username_validation]},model:{value:t.username,callback:function(e){t.username=e},expression:"username"}}),e(yt.Z,{staticStyle:{"margin-top":"2px"},attrs:{outlined:"",placeholder:"パスワード",dense:t.is_form_dense,type:t.password_showing?"text":"password","append-icon":t.password_showing?"mdi-eye":"mdi-eye-off",rules:[t.password_validation]},on:{"click:append":function(e){t.password_showing=!t.password_showing}},model:{value:t.password,callback:function(e){t.password=e},expression:"password"}}),e(j.Z,{staticClass:"register-button mt-5",attrs:{color:"secondary",depressed:"",width:"100%",height:"56"},on:{click:function(e){return t.register()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:person-add-20-filled",height:"24"}}),t._v("アカウントを作成 ")],1)],1)],1)],1)],1)],1)},ie=[],ae=o["default"].extend({name:"Register",components:{Header:It,Navigation:Rt},data(){return{is_form_dense:ht.isSmartphoneHorizontal(),username:null,username_validation:t=>""===t||null===t?"ユーザー名を入力してください。":!1!==/^.{2,}$/.test(t)||"ユーザー名は2文字以上で入力してください。",password:null,password_showing:!0,password_validation:t=>""===t||null===t?"パスワードを入力してください。":!1!==/^[a-zA-Z0-9!-/:-@¥[-`{-~]{4,}$/.test(t)||"パスワードは4文字以上の半角英数記号を入力してください。"}},computed:Object.assign({},(0,a.Kc)(R)),async created(){await this.userStore.fetchUser(),this.userStore.is_logged_in&&await this.$router.replace({path:"/settings/account"})},methods:{async register(){if(!1===this.$refs.register.validate())return;if(null===this.username||null===this.password)return;const t=await this.userStore.register(this.username,this.password);!1!==t&&await this.$router.replace({path:"/settings/account"})}}}),ne=ae,re=(0,h.Z)(ne,se,ie,!1,null,"6533f3d0",null),oe=re.exports,le=s(9789),ce=s(271),_e=s(5787),de=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("SettingsBase",[e("h2",{staticClass:"settings__heading"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"settings__back-button",attrs:{to:"/settings/"}},[e("Icon",{attrs:{icon:"fluent:arrow-left-12-filled",width:"25px"}})],1),e("Icon",{attrs:{icon:"fluent:person-20-filled",width:"25px"}}),e("span",{staticClass:"ml-2"},[t._v("アカウント")])],1),e("div",{staticClass:"settings__content",class:{"settings__content--loading":t.is_loading}},[null===t.userStore.user?e("div",{staticClass:"account"},[e("div",{staticClass:"account-wrapper"},[e("img",{staticClass:"account__icon",attrs:{src:"/assets/images/account-icon-default.png"}}),e("div",{staticClass:"account__info"},[e("div",{staticClass:"account__info-name"},[e("span",{staticClass:"account__info-name-text"},[t._v("ログインしていません")])]),e("span",{staticClass:"account__info-id"},[t._v("Not logged in")])])]),e(j.Z,{staticClass:"account__login ml-auto",attrs:{color:"secondary",width:"140",height:"56",depressed:"",to:"/login/"}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fa:sign-in"}}),t._v("ログイン ")],1)],1):t._e(),null!==t.userStore.user?e("div",{staticClass:"account"},[e("div",{staticClass:"account-wrapper"},[e("img",{staticClass:"account__icon",attrs:{src:t.userStore.user_icon_url}}),e("div",{staticClass:"account__info"},[e("div",{staticClass:"account__info-name"},[e("span",{staticClass:"account__info-name-text"},[t._v(t._s(t.userStore.user.name))]),t.userStore.user.is_admin?e("span",{staticClass:"account__info-admin"},[t._v("管理者")]):t._e()]),e("span",{staticClass:"account__info-id"},[t._v("User ID: "+t._s(t.userStore.user.id))])])]),e(j.Z,{staticClass:"account__login ml-auto",attrs:{color:"secondary",width:"140",height:"56",depressed:""},on:{click:function(e){return t.userStore.logout()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fa:sign-out"}}),t._v("ログアウト ")],1)],1):t._e(),!1===t.userStore.is_logged_in?e("div",{staticClass:"account-register"},[e("div",{staticClass:"account-register__heading"},[t._v(" KonomiTV アカウントにログインすると、"),e("br"),t._v("より便利な機能が使えます! ")]),e("div",{staticClass:"account-register__feature"},[e("div",{staticClass:"account-feature"},[e("Icon",{staticClass:"account-feature__icon",attrs:{icon:"bi:chat-left-text-fill"}}),e("div",{staticClass:"account-feature__info"},[e("span",{staticClass:"account-feature__info-heading"},[t._v("ニコニコ実況にコメントする")]),e("span",{staticClass:"account-feature__info-text"},[t._v("テレビを見ながらニコニコ実況にコメントできます。別途、ニコニコアカウントとの連携が必要です。")])])],1),e("div",{staticClass:"account-feature"},[e("Icon",{staticClass:"account-feature__icon",attrs:{icon:"fa-brands:twitter"}}),e("div",{staticClass:"account-feature__info"},[e("span",{staticClass:"account-feature__info-heading"},[t._v("Twitter 連携機能")]),e("span",{staticClass:"account-feature__info-text"},[t._v("テレビを見ながら Twitter にツイートしたり、検索したツイートをリアルタイムで表示できます。別途、Twitter アカウントとの連携が必要です。")])])],1),e("div",{staticClass:"account-feature"},[e("Icon",{staticClass:"account-feature__icon",attrs:{icon:"fluent:arrow-sync-20-filled"}}),e("div",{staticClass:"account-feature__info"},[e("span",{staticClass:"account-feature__info-heading"},[t._v("設定をデバイス間で同期")]),e("span",{staticClass:"account-feature__info-text"},[t._v("ピン留めしたチャンネルなど、ブラウザに保存されている各種設定をブラウザやデバイスをまたいで同期できます。")])])],1),e("div",{staticClass:"account-feature"},[e("Icon",{staticClass:"account-feature__icon",attrs:{icon:"fa-solid:sliders-h"}}),e("div",{staticClass:"account-feature__info"},[e("span",{staticClass:"account-feature__info-heading"},[t._v("環境設定をブラウザから変更")]),e("span",{staticClass:"account-feature__info-text"},[t._v("管理者権限があれば、環境設定をブラウザから変更できます。一番最初に作成されたアカウントには、自動で管理者権限が付与されます。")])])],1)]),e("div",{staticClass:"account-register__description"},[t._v(" KonomiTV アカウントの作成に必要なものは"),e("br",{staticClass:"smartphone-vertical-only"}),t._v("ユーザー名とパスワードだけです。"),e("br"),t._v(" アカウントはローカルにインストールした"),e("br",{staticClass:"smartphone-vertical-only"}),t._v(" KonomiTV サーバーごとに保存されます。"),e("br"),t._v(" 外部のサービスには保存されませんので、ご安心ください。"),e("br")]),e(j.Z,{staticClass:"account-register__button",attrs:{color:"secondary",width:"100%","max-width":"250",height:"50",depressed:"",to:"/register/"}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:person-add-20-filled",height:"24"}}),t._v("アカウントを作成 ")],1)],1):t._e(),!0===t.userStore.is_logged_in?e("div",[e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"sync_settings"}},[t._v("設定をデバイス間で同期する")]),e("label",{staticClass:"settings__item-label",attrs:{for:"sync_settings"}},[t._v(" KonomiTV では、設定を同じアカウントでログインしているデバイス間で同期できます!"),e("br"),t._v(" 同期をオンにすると、同期をオンにしているすべてのデバイスで共通の設定が使えます。ピン留めチャンネルやハッシュタグリストなども同期されます。"),e("br"),t._v(" なお、デバイス固有の設定(画質設定など)は、同期後も各デバイスで個別に反映されます。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"sync_settings",inset:"","hide-details":""},model:{value:t.sync_settings,callback:function(e){t.sync_settings=e},expression:"sync_settings"}})],1),e(le.Z,{attrs:{"max-width":"530"},model:{value:t.sync_settings_dialog,callback:function(e){t.sync_settings_dialog=e},expression:"sync_settings_dialog"}},[e(pt.Z,[e(gt.EB,{staticClass:"justify-center"},[t._v("設定データの競合")]),e(gt.ZB,[t._v(" このデバイスの設定と、サーバーに保存されている設定が競合しています。"),e("br"),t._v(" 一度上書きすると、元に戻すことはできません。慎重に選択してください。"),e("br")]),e("div",{staticClass:"d-flex flex-column px-4 pb-4 settings__conflict-dialog"},[e(j.Z,{staticClass:"settings__save-button error--text text--lighten-1",attrs:{depressed:""},on:{click:function(e){return t.overrideServerSettingsFromClient()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:document-arrow-up-16-filled",height:"22px"}}),t._v(" サーバーに保存されている設定を、"),e("br",{staticClass:"smartphone-vertical-only"}),t._v("このデバイスの設定で上書きする ")],1),e(j.Z,{staticClass:"settings__save-button error--text text--lighten-1 mt-3",attrs:{depressed:""},on:{click:function(e){return t.overrideClientSettingsFromServer()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:document-arrow-down-16-filled",height:"22px"}}),t._v(" このデバイスの設定を、"),e("br",{staticClass:"smartphone-vertical-only"}),t._v("サーバーに保存されている設定で上書きする ")],1),e(j.Z,{staticClass:"settings__save-button mt-3",attrs:{depressed:""},on:{click:function(e){t.sync_settings_dialog=!1}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:dismiss-16-filled",height:"22px"}}),t._v(" キャンセル ")],1)],1)],1)],1),e(ft.Z,{ref:"settings_username",staticClass:"settings__item",on:{submit:function(t){t.preventDefault()}}},[e("div",{staticClass:"settings__item-heading"},[t._v("ユーザー名")]),e("div",{staticClass:"settings__item-label"},[t._v(" KonomiTV アカウントのユーザー名を設定します。アルファベットだけでなく日本語や記号も使えます。"),e("br"),t._v(" 同じ KonomiTV サーバー上の他のアカウントと同じユーザー名には変更できません。"),e("br")]),e(yt.Z,{staticClass:"settings__item-form",attrs:{outlined:"",placeholder:"ユーザー名",dense:t.is_form_dense,rules:[t.settings_username_validation]},model:{value:t.settings_username,callback:function(e){t.settings_username=e},expression:"settings_username"}})],1),e(j.Z,{staticClass:"settings__save-button",attrs:{depressed:""},on:{click:function(e){return t.updateAccountInfo("username")}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:save-16-filled",height:"24px"}}),t._v("ユーザー名を更新 ")],1),e(ft.Z,{staticClass:"settings__item",on:{submit:function(t){t.preventDefault()}}},[e("div",{staticClass:"settings__item-heading"},[t._v("アイコン画像")]),e("div",{staticClass:"settings__item-label"},[t._v(" KonomiTV アカウントのアイコン画像を設定します。"),e("br"),t._v(" アップロードされた画像は自動で 400×400 の正方形にリサイズされます。"),e("br")]),e(ce.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",placeholder:"アイコン画像を選択",dense:t.is_form_dense,accept:"image/jpeg, image/png","prepend-icon":"","prepend-inner-icon":"mdi-paperclip"},model:{value:t.settings_icon,callback:function(e){t.settings_icon=e},expression:"settings_icon"}})],1),e(j.Z,{staticClass:"settings__save-button mt-5",attrs:{depressed:""},on:{click:function(e){return t.updateAccountIcon()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:save-16-filled",height:"24px"}}),t._v("アイコン画像を更新 ")],1),e(ft.Z,{ref:"settings_password",staticClass:"settings__item",on:{submit:function(t){t.preventDefault()}}},[e("div",{staticClass:"settings__item-heading"},[t._v("新しいパスワード")]),e("div",{staticClass:"settings__item-label"},[t._v(" KonomiTV アカウントの新しいパスワードを設定します。"),e("br")]),e(yt.Z,{staticClass:"settings__item-form",attrs:{outlined:"",placeholder:"新しいパスワード",dense:t.is_form_dense,type:t.settings_password_showing?"text":"password","append-icon":t.settings_password_showing?"mdi-eye":"mdi-eye-off",rules:[t.settings_password_validation]},on:{"click:append":function(e){t.settings_password_showing=!t.settings_password_showing}},model:{value:t.settings_password,callback:function(e){t.settings_password=e},expression:"settings_password"}})],1),e(j.Z,{staticClass:"settings__save-button",attrs:{depressed:""},on:{click:function(e){return t.updateAccountInfo("password")}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:save-16-filled",height:"24px"}}),t._v("パスワードを更新 ")],1),e(vt.Z,{staticClass:"mt-6"}),e("div",{staticClass:"settings__item mt-6"},[e("div",{staticClass:"settings__item-heading error--text text--lighten-1"},[t._v("アカウントを削除")]),e("div",{staticClass:"settings__item-label"},[t._v(" 現在ログインしている KonomiTV アカウントを削除します。"),e("br"),e("b",[t._v("アカウントに紐づくすべてのデータが削除されます。")]),t._v("元に戻すことはできません。"),e("br")])]),e(le.Z,{attrs:{"max-width":"385"},scopedSlots:t._u([{key:"activator",fn:function({on:s}){return[e(j.Z,t._g({staticClass:"settings__save-button error mt-5",attrs:{depressed:""}},s),[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:delete-16-filled",height:"24px"}}),t._v("アカウントを削除 ")],1)]}}],null,!1,1849668703),model:{value:t.account_delete_confirm_dialog,callback:function(e){t.account_delete_confirm_dialog=e},expression:"account_delete_confirm_dialog"}},[e(pt.Z,[e(gt.EB,{staticClass:"justify-center pt-6 font-weight-bold"},[t._v("本当にアカウントを削除しますか?")]),e(gt.ZB,{staticClass:"pt-2 pb-0"},[t._v(" アカウントに紐づくすべてのデータが削除されます。元に戻すことはできません。"),e("br"),t._v(" 本当にアカウントを削除しますか? ")]),e(gt.h7,{staticClass:"pt-4 px-6 pb-5"},[e(kt.Z),e(j.Z,{attrs:{color:"text",text:""},on:{click:function(e){t.account_delete_confirm_dialog=!1}}},[t._v("キャンセル")]),e(j.Z,{attrs:{color:"error"},on:{click:function(e){return t.deleteAccount()}}},[t._v("削除")])],1)],1)],1)],1):t._e()])])},ue=[],me=s(248),he=s(1908),pe=s(1769),ge=s(8228),ve=s(1969),fe=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"route-container"},[e("Header"),e("main",[e("Navigation"),e(pt.Z,{staticClass:"settings-container d-flex px-5 py-5 mx-auto background",attrs:{elevation:"0",width:"100%","max-width":"1000"}},[e("div",[e(ve.Z,{staticClass:"settings-navigation flex-shrink-0 background",attrs:{permanent:"",width:"195",height:"auto"}},[e(he.Z,{staticClass:"px-4"},[e(pe.km,[e("h1",[t._v("設定")])])],1),e(me.Z,{staticClass:"mt-2 px-0",attrs:{nav:""}},[e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/general"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{staticStyle:{padding:"0 3px"},attrs:{icon:"fa-solid:sliders-h",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("全般")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/caption"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{attrs:{icon:"fluent:subtitles-16-filled",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("字幕")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/capture"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{attrs:{icon:"fluent:image-multiple-16-filled",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("キャプチャ")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/account"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{attrs:{icon:"fluent:person-20-filled",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("アカウント")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/jikkyo"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{staticStyle:{padding:"0 2px"},attrs:{icon:"bi:chat-left-text-fill",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("ニコニコ実況")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/twitter"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{staticStyle:{padding:"0 1px"},attrs:{icon:"fa-brands:twitter",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("Twitter")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/server"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{attrs:{icon:"fluent:server-surface-16-filled",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("サーバー設定")])],1)],1)],1)],1)],1),e(pt.Z,{staticClass:"settings ml-5 px-7 py-7 lighten-1",attrs:{width:"100%"}},[t._t("default")],2)],1)],1)],1)},we=[],ye=o["default"].extend({name:"Settings-Base",components:{Header:It,Navigation:Rt}}),be=ye,Ce=(0,h.Z)(be,fe,we,!1,null,"d0f5a998",null),ke=Ce.exports,xe=o["default"].extend({name:"Settings-Account",components:{SettingsBase:ke},data(){return{is_form_dense:ht.isSmartphoneHorizontal(),is_loading:!0,settings_username:null,settings_username_validation:t=>""===t||null===t?"ユーザー名を入力してください。":!1!==/^.{2,}$/.test(t)||"ユーザー名は2文字以上で入力してください。",settings_password:null,settings_password_showing:!1,settings_password_validation:t=>""===t||null===t?"パスワードを入力してください。":!1!==/^[a-zA-Z0-9!-/:-@¥[-`{-~]{4,}$/.test(t)||"パスワードは4文字以上の半角英数記号を入力してください。",settings_icon:null,account_delete_confirm_dialog:!1,sync_settings:st().settings.sync_settings,sync_settings_dialog:!1}},computed:Object.assign({},(0,a.Kc)(st,R)),async created(){await this.userStore.fetchUser(),this.is_loading=!1},watch:{async sync_settings(){if(!0===this.sync_settings&&!1===this.sync_settings_dialog){const t=this.settingsStore.getSyncableClientSettings(),e=JSON.stringify(t),s=await q.fetchClientSettings();if(null===s)return void this.$message.error("サーバーから設定データを取得できませんでした。");const i=JSON.stringify(s);e!==i?(this.sync_settings_dialog=!0,this.sync_settings=!1):this.settingsStore.settings.sync_settings=!0}else!1===this.sync_settings&&!1===this.sync_settings_dialog&&(this.settingsStore.settings.sync_settings=!1)}},methods:{async overrideServerSettingsFromClient(){await this.settingsStore.syncClientSettingsToServer(!0),this.settingsStore.settings.sync_settings=!0,this.sync_settings=!0,this.sync_settings_dialog=!1},async overrideClientSettingsFromServer(){await this.settingsStore.syncClientSettingsFromServer(!0),this.settingsStore.settings.sync_settings=!0,this.sync_settings=!0,this.sync_settings_dialog=!1},async updateAccountInfo(t){if("username"===t){if(!1===this.$refs.settings_username.validate())return}else if(!1===this.$refs.settings_password.validate())return;if("username"===t){if(null===this.settings_username)return;await this.userStore.updateUser({username:this.settings_username})}else{if(null===this.settings_password)return;await this.userStore.updateUser({password:this.settings_password})}},async updateAccountIcon(){null!==this.settings_icon?await this.userStore.updateUserIcon(this.settings_icon):this.$message.error("アップロードする画像を選択してください!")},async deleteAccount(){this.account_delete_confirm_dialog=!1,await this.userStore.deleteUser()}}}),Se=xe,Oe=(0,h.Z)(Se,de,ue,!1,null,"7749b102",null),Te=Oe.exports,je=s(5022),Ie=s(2059),Pe=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("SettingsBase",[e("h2",{staticClass:"settings__heading"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"settings__back-button",attrs:{to:"/settings/"}},[e("Icon",{attrs:{icon:"fluent:arrow-left-12-filled",width:"25px"}})],1),e("Icon",{attrs:{icon:"fluent:subtitles-16-filled",width:"25px"}}),e("span",{staticClass:"ml-3"},[t._v("字幕")])],1),e("div",{staticClass:"settings__content"},[e("div",{staticClass:"settings__item"},[e("label",{staticClass:"settings__item-heading"},[t._v("字幕のフォント")]),e("label",{staticClass:"settings__item-label"},[t._v(" プレイヤーで字幕表示をオンにしているときの、字幕のフォントを設定します。"),e("br")]),e(je.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",dense:t.is_form_dense,items:t.caption_font},model:{value:t.settingsStore.settings.caption_font,callback:function(e){t.$set(t.settingsStore.settings,"caption_font",e)},expression:"settingsStore.settings.caption_font"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"always_border_caption_text"}},[t._v("字幕の文字を常に縁取りする")]),e("label",{staticClass:"settings__item-label",attrs:{for:"always_border_caption_text"}},[t._v(" 字幕表示時、縁取りをオンにすると、字幕が見やすくきれいになります。とくに理由がなければ、オンにしておくのがおすすめです。"),e("br"),t._v(" この設定がオフのときも、字幕データ側で縁取りが指定されていれば、オンのときと同様に縁取り付きで描画されます。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"always_border_caption_text",inset:"","hide-details":""},model:{value:t.settingsStore.settings.always_border_caption_text,callback:function(e){t.$set(t.settingsStore.settings,"always_border_caption_text",e)},expression:"settingsStore.settings.always_border_caption_text"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"specify_caption_opacity"}},[t._v("字幕の不透明度を指定する")]),e("label",{staticClass:"settings__item-label",attrs:{for:"specify_caption_opacity"}},[t._v(" 字幕表示時、不透明度を自分で指定するか設定できます。"),e("br"),t._v(" この設定がオフのときは、字幕データ側で指定されている不透明度で描画します。とくに理由がなければ、オフにしておくのがおすすめです。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"specify_caption_opacity",inset:"","hide-details":""},model:{value:t.settingsStore.settings.specify_caption_opacity,callback:function(e){t.$set(t.settingsStore.settings,"specify_caption_opacity",e)},expression:"settingsStore.settings.specify_caption_opacity"}})],1),e("div",{staticClass:"settings__item",class:{"settings__item--disabled":!1===t.settingsStore.settings.specify_caption_opacity}},[e("label",{staticClass:"settings__item-heading"},[t._v("字幕の不透明度")]),e("label",{staticClass:"settings__item-label"},[t._v(" 上の [字幕の不透明度を指定する] をオンに設定したときのみ有効です。不透明度を 0 に設定すれば、字幕の背景を非表示にできます。"),e("br")]),e("div",{ref:"caption_opacity",staticClass:"settings__item-label"},[e(Ie.Z,{staticClass:"settings__item-form",attrs:{ticks:"always","thumb-label":"","hide-details":"",min:0,max:1,step:.05,disabled:!1===t.settingsStore.settings.specify_caption_opacity},model:{value:t.settingsStore.settings.caption_opacity,callback:function(e){t.$set(t.settingsStore.settings,"caption_opacity",e)},expression:"settingsStore.settings.caption_opacity"}})],1)]),e(vt.Z,{staticClass:"mt-6"}),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"tv_show_superimpose"}},[t._v("テレビをみるときに文字スーパーを表示する")]),e("label",{staticClass:"settings__item-label",attrs:{for:"tv_show_superimpose"}},[t._v(" 文字スーパーは、緊急地震速報の赤テロップや、NHK BS のニュース速報のテロップなどで利用されています。とくに理由がなければ、オンにしておくのがおすすめです。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"tv_show_superimpose",inset:"","hide-details":""},model:{value:t.settingsStore.settings.tv_show_superimpose,callback:function(e){t.$set(t.settingsStore.settings,"tv_show_superimpose",e)},expression:"settingsStore.settings.tv_show_superimpose"}})],1)],1)])},Ze=[],ze=o["default"].extend({name:"Settings-Caption",components:{SettingsBase:ke},data(){return{is_form_dense:ht.isSmartphoneHorizontal(),caption_font:[{text:"Windows TV ゴシック",value:"Windows TV Gothic"},{text:"Windows TV 丸ゴシック",value:"Windows TV MaruGothic"},{text:"Windows TV 太丸ゴシック",value:"Windows TV FutoMaruGothic"},{text:"ヒラギノTV丸ゴ",value:"Hiragino TV Sans Rd S"},{text:"新丸ゴ ARIB",value:"TT-ShinMGo-regular"},{text:"Rounded M+ 1m for ARIB",value:"Rounded M+ 1m for ARIB"},{text:"Noto Sans JP",value:"Noto Sans JP Caption"},{text:"デフォルトのフォント",value:"sans-serif"}]}},computed:Object.assign({},(0,a.Kc)(st))}),$e=ze,Ae=(0,h.Z)($e,Pe,Ze,!1,null,null,null),De=Ae.exports,Be=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("SettingsBase",[e("h2",{staticClass:"settings__heading"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"settings__back-button",attrs:{to:"/settings/"}},[e("Icon",{attrs:{icon:"fluent:arrow-left-12-filled",width:"25px"}})],1),e("Icon",{attrs:{icon:"fluent:image-multiple-16-filled",width:"26px"}}),e("span",{staticClass:"ml-2"},[t._v("キャプチャ")])],1),e("div",{staticClass:"settings__content"},[e("div",{staticClass:"settings__item settings__item--switch settings__item--sync-disabled"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"capture_copy_to_clipboard"}},[t._v("キャプチャをクリップボードにコピーする")]),e("label",{staticClass:"settings__item-label",attrs:{for:"capture_copy_to_clipboard"}},[t._v(" この設定をオンにすると、撮ったキャプチャ画像がクリップボードにもコピーされます。"),e("br"),t._v(" クリップボードの履歴をサポートしていない OS では、この設定をオンにしてキャプチャを撮ると、以前のクリップボードが上書きされます。注意してください。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"capture_copy_to_clipboard",inset:"","hide-details":""},model:{value:t.settingsStore.settings.capture_copy_to_clipboard,callback:function(e){t.$set(t.settingsStore.settings,"capture_copy_to_clipboard",e)},expression:"settingsStore.settings.capture_copy_to_clipboard"}})],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("キャプチャの保存先")]),e("div",{staticClass:"settings__item-label"},[e("p",[t._v(" キャプチャした画像をブラウザでダウンロードするか、KonomiTV サーバーにアップロードするかを設定します。"),e("br"),t._v(" ブラウザでのダウンロードと、KonomiTV サーバーへのアップロードを両方同時に行うこともできます。"),e("br")]),e("p",[t._v(" ブラウザでダウンロードすると、視聴中のデバイスのダウンロードフォルダに保存されます。"),e("br"),t._v(" 視聴中のデバイスにそのまま保存されるためシンプルですが、保存先のフォルダを変更できないこと、PC 版 Chrome では毎回ダウンロードバーが表示されてしまったり、iOS Safari (PWA モード) ではダウンロードするとファイル概要画面が表示され再生が中断してしまったりなど、視聴に支障することがデメリットです (将来的には、iOS / Android アプリ版や拡張機能などで解消される予定) 。"),e("br")]),e("p",[t._v(" KonomiTV サーバーにアップロードすると、環境設定で指定されたキャプチャ保存フォルダに保存されます。視聴したデバイスにかかわらず、今までに撮ったキャプチャをひとつのフォルダにまとめて保存できます。"),e("br"),t._v(" 他のデバイスでキャプチャを見るにはキャプチャ保存フォルダをネットワークに共有する必要があること、スマホ・タブレットではネットワーク上のフォルダへのアクセスがやや面倒なことがデメリットです。(将来的には、保存フォルダ内のキャプチャを Google フォトのように表示する機能を追加予定)"),e("br")])]),e(je.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",dense:t.is_form_dense,items:t.capture_save_mode},model:{value:t.settingsStore.settings.capture_save_mode,callback:function(e){t.$set(t.settingsStore.settings,"capture_save_mode",e)},expression:"settingsStore.settings.capture_save_mode"}})],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("字幕表示時のキャプチャの保存モード")]),e("div",{staticClass:"settings__item-label"},[t._v(" 字幕表示時、キャプチャした画像に字幕を合成するかを設定します。"),e("br"),t._v(" 映像のみのキャプチャと、字幕を合成したキャプチャを両方同時に保存することもできます。"),e("br"),t._v(" なお、字幕非表示時は、常に映像のみ (+コメント付きキャプチャではコメントを合成して) 保存されます。"),e("br")]),e(je.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",dense:t.is_form_dense,items:t.capture_caption_mode},model:{value:t.settingsStore.settings.capture_caption_mode,callback:function(e){t.$set(t.settingsStore.settings,"capture_caption_mode",e)},expression:"settingsStore.settings.capture_caption_mode"}})],1)])])},Ne=[],Ke=o["default"].extend({name:"Settings-Capture",components:{SettingsBase:ke},data(){return{is_form_dense:ht.isSmartphoneHorizontal(),capture_save_mode:[{text:"ブラウザでダウンロード",value:"Browser"},{text:"KonomiTV サーバーにアップロード",value:"UploadServer"},{text:"ブラウザでのダウンロードと、KonomiTV サーバーへのアップロードを両方行う",value:"Both"}],capture_caption_mode:[{text:"映像のみのキャプチャを保存する",value:"VideoOnly"},{text:"字幕を合成したキャプチャを保存する",value:"CompositingCaption"},{text:"映像のみのキャプチャと、字幕を合成したキャプチャを両方保存する",value:"Both"}]}},computed:Object.assign({},(0,a.Kc)(st))}),Le=Ke,Ee=(0,h.Z)(Le,Be,Ne,!1,null,null,null),He=Ee.exports,Me=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("SettingsBase",[e("h2",{staticClass:"settings__heading"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"settings__back-button",attrs:{to:"/settings/"}},[e("Icon",{attrs:{icon:"fluent:arrow-left-12-filled",width:"25px"}})],1),e("Icon",{attrs:{icon:"fa-solid:sliders-h",width:"19px"}}),e("span",{staticClass:"ml-3"},[t._v("全般")])],1),e("div",{staticClass:"settings__content"},[e("div",{staticClass:"settings__item settings__item--sync-disabled"},[e("div",{staticClass:"settings__item-heading"},[t._v("テレビのデフォルトのストリーミング画質")]),e("div",{staticClass:"settings__item-label"},[t._v(" テレビをライブストリーミングするときのデフォルトの画質を設定します。"),e("br"),t._v(" ストリーミング画質はプレイヤーの設定からいつでも切り替えられます。"),e("br")]),e("div",{staticClass:"settings__item-label"},[t._v(" [1080p (60fps)] は、通常 30fps (60i) の映像を補間し、より滑らか(ぬるぬる)な映像で視聴できます!"),e("br"),t._v(" [1080p (60fps)] で視聴するときは、環境設定の [利用するエンコーダー] をハードウェアエンコーダーに設定してください。FFmpeg (ソフトウェアエンコーダー) では、再生に支障が出ることがあります。"),e("br")]),e(je.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",dense:t.is_form_dense,items:t.tv_streaming_quality},model:{value:t.settingsStore.settings.tv_streaming_quality,callback:function(e){t.$set(t.settingsStore.settings,"tv_streaming_quality",e)},expression:"settingsStore.settings.tv_streaming_quality"}})],1),e("div",{staticClass:"settings__item settings__item--switch settings__item--sync-disabled",class:{"settings__item--disabled":!1===t.PlayerUtils.isHEVCVideoSupported()}},[e("label",{staticClass:"settings__item-heading",attrs:{for:"tv_data_saver_mode"}},[t._v("テレビを通信節約モードで視聴する")]),e("label",{staticClass:"settings__item-label",attrs:{for:"tv_data_saver_mode"}},[t._v(" 通信節約モードでは、H.265 / HEVC という圧縮率の高いコーデックを使い、画質はほぼそのまま、通信量を通常の 1/2 程度に抑えながら視聴できます!"),e("br"),t._v(" 通信節約モードで視聴するときは、環境設定の [利用するエンコーダー] をハードウェアエンコーダーに設定してください。FFmpeg (ソフトウェアエンコーダー) では、再生に支障が出る可能性が高いです。"),e("br"),!1===t.PlayerUtils.isHEVCVideoSupported()&&!1===t.Utils.isFirefox()?e("p",{staticClass:"mt-1 mb-0 error--text lighten-1"},[t._v(" このデバイスでは通信節約モードがサポートされていません。 ")]):t._e(),!1===t.PlayerUtils.isHEVCVideoSupported()&&!0===t.Utils.isFirefox()?e("p",{staticClass:"mt-1 mb-0 error--text lighten-1"},[t._v(" お使いの Firefox ブラウザでは通信節約モードがサポートされていません。 ")]):t._e()]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"tv_data_saver_mode",inset:"","hide-details":"",disabled:!1===t.PlayerUtils.isHEVCVideoSupported()},model:{value:t.settingsStore.settings.tv_data_saver_mode,callback:function(e){t.$set(t.settingsStore.settings,"tv_data_saver_mode",e)},expression:"settingsStore.settings.tv_data_saver_mode"}})],1),e("div",{staticClass:"settings__item settings__item--switch settings__item--sync-disabled"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"tv_low_latency_mode"}},[t._v("テレビを低遅延で視聴する")]),e("label",{staticClass:"settings__item-label",attrs:{for:"tv_low_latency_mode"}},[t._v(" 低遅延ストリーミングをオンにすると、"),e("b",[t._v("放送波との遅延を最短 0.9 秒に抑えて視聴できます!")]),e("br"),t._v(" また、約 3 秒以上遅延したときに少しだけ再生速度を早める (1.1x) ことで、滑らかにストリーミングの遅延を取り戻します。"),e("br"),t._v(" 宅外視聴などのネットワークが不安定になりがちな環境では、低遅延ストリーミングをオフにしてみると、映像のカクつきを改善できるかもしれません。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"tv_low_latency_mode",inset:"","hide-details":""},model:{value:t.settingsStore.settings.tv_low_latency_mode,callback:function(e){t.$set(t.settingsStore.settings,"tv_low_latency_mode",e)},expression:"settingsStore.settings.tv_low_latency_mode"}})],1),e(vt.Z,{staticClass:"mt-6"}),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("デフォルトのパネルの表示状態")]),e("div",{staticClass:"settings__item-label"},[t._v(" 視聴画面を開いたときに、右側のパネルをどう表示するかを設定します。"),e("br")]),e(je.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",dense:t.is_form_dense,items:t.panel_display_state},model:{value:t.settingsStore.settings.panel_display_state,callback:function(e){t.$set(t.settingsStore.settings,"panel_display_state",e)},expression:"settingsStore.settings.panel_display_state"}})],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("テレビをみるときにデフォルトで表示されるパネルのタブ")]),e("div",{staticClass:"settings__item-label"},[t._v(" テレビの視聴画面を開いたときに、右側のパネルで最初に表示されるタブを設定します。"),e("br")]),e(je.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",dense:t.is_form_dense,items:t.tv_panel_active_tab},model:{value:t.settingsStore.settings.tv_panel_active_tab,callback:function(e){t.$set(t.settingsStore.settings,"tv_panel_active_tab",e)},expression:"settingsStore.settings.tv_panel_active_tab"}})],1),e(vt.Z,{staticClass:"mt-6"}),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"tv_show_superimpose"}},[t._v("チャンネル選局のキーボードショートカットを "+t._s(t.Utils.AltOrOption())+" + 数字キー/テンキーに変更する")]),e("label",{staticClass:"settings__item-label",attrs:{for:"tv_show_superimpose"}},[t._v(" この設定をオンにすると、数字キーまたはテンキーに対応するリモコン番号(1~12)のチャンネルに切り替える際、"+t._s(t.Utils.AltOrOption())+" キーを同時に押す必要があります。"),e("br"),t._v(" コメントやツイートを入力しようとして誤って数字キーを押してしまい、チャンネルが変わってしまう事態を避けたい方におすすめです。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"tv_show_superimpose",inset:"","hide-details":""},model:{value:t.settingsStore.settings.tv_channel_selection_requires_alt_key,callback:function(e){t.$set(t.settingsStore.settings,"tv_channel_selection_requires_alt_key",e)},expression:"settingsStore.settings.tv_channel_selection_requires_alt_key"}})],1),e(vt.Z,{staticClass:"mt-6"}),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("設定をエクスポート")]),e("div",{staticClass:"settings__item-label"},[t._v(" このデバイス(ブラウザ)に保存されている設定データを、エクスポート(ダウンロード)できます。"),e("br"),t._v(" ダウンロードした設定データ (KonomiTV-Settings.json) は、[設定をインポート] からインポートできます。異なるサーバーの KonomiTV を同じ設定で使いたいときなどに使ってください。"),e("br")])]),e(j.Z,{staticClass:"settings__save-button mt-4",attrs:{depressed:""},on:{click:function(e){return t.exportSettings()}}},[e("Icon",{staticClass:"mr-3",attrs:{icon:"fa6-solid:download",height:"19px"}}),t._v("設定をエクスポート ")],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading error--text text--lighten-1"},[t._v("設定をインポート")]),e("div",{staticClass:"settings__item-label"},[t._v(" [設定をエクスポート] でダウンロードした設定データを、このデバイス(ブラウザ)にインポートできます。"),e("br"),t._v(" 設定をインポートすると、"),e("b",[t._v("現在のデバイス設定はすべて上書きされます。")]),t._v("元に戻すことはできません。"),e("br"),t._v(" 設定のデバイス間同期がオンのときは、"),e("b",[t._v("同期が有効なすべてのデバイスに反映されます。")]),t._v("十分ご注意ください。"),e("br")]),e(ce.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",placeholder:"設定データ (KonomiTV-Settings.json) を選択",dense:t.is_form_dense,accept:"application/json","prepend-icon":"","prepend-inner-icon":"mdi-paperclip"},model:{value:t.import_settings_file,callback:function(e){t.import_settings_file=e},expression:"import_settings_file"}})],1),e(j.Z,{staticClass:"settings__save-button error mt-5",attrs:{depressed:""},on:{click:function(e){return t.importSettings()}}},[e("Icon",{staticClass:"mr-3",attrs:{icon:"fa6-solid:upload",height:"19px"}}),t._v("設定をインポート ")],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading error--text text--lighten-1"},[t._v("設定を初期状態にリセット")]),e("div",{staticClass:"settings__item-label"},[t._v(" このデバイス(ブラウザ)に保存されている設定データを、初期状態のデフォルト値にリセットできます。"),e("br"),t._v(" 設定をリセットすると、元に戻すことはできません。"),e("br"),t._v(" 設定のデバイス間同期がオンのときは、"),e("b",[t._v("同期が有効なすべてのデバイスに反映されます。")]),t._v("十分ご注意ください。"),e("br")])]),e(j.Z,{staticClass:"settings__save-button error mt-5",attrs:{depressed:""},on:{click:function(e){return t.resetSettings()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"material-symbols:device-reset-rounded",height:"23px"}}),t._v("設定をリセット ")],1)],1)])},Ue=[];const Ve=[{text:"1080p (60fps) (約4.50GB/h / 平均10.0Mbps)",value:"1080p-60fps"},{text:"1080p (約4.50GB/h / 平均10.0Mbps)",value:"1080p"},{text:"810p (約2.62GB/h / 平均5.8Mbps)",value:"810p"},{text:"720p (約2.18GB/h / 平均4.9Mbps)",value:"720p"},{text:"540p (約1.52GB/h / 平均3.4Mbps)",value:"540p"},{text:"480p (約1.06GB/h / 平均2.3Mbps)",value:"480p"},{text:"360p (約0.60GB/h / 平均1.3Mbps)",value:"360p"},{text:"240p (約0.35GB/h / 平均0.8Mbps)",value:"240p"}],Re=[{text:"1080p (60fps) (約1.80GB/h / 平均4.0Mbps)",value:"1080p-60fps"},{text:"1080p (約1.37GB/h / 平均3.0Mbps)",value:"1080p"},{text:"810p (約1.05GB/h / 平均2.3Mbps)",value:"810p"},{text:"720p (約0.82GB/h / 平均1.8Mbps)",value:"720p"},{text:"540p (約0.53GB/h / 平均1.2Mbps)",value:"540p"},{text:"480p (約0.46GB/h / 平均1.0Mbps)",value:"480p"},{text:"360p (約0.30GB/h / 平均0.7Mbps)",value:"360p"},{text:"240p (約0.20GB/h / 平均0.4Mbps)",value:"240p"}];var Fe=o["default"].extend({name:"Settings-General",components:{SettingsBase:ke},data(){return{Utils:ht,PlayerUtils:at,is_form_dense:ht.isSmartphoneHorizontal(),tv_streaming_quality:Ve,panel_display_state:[{text:"前回の状態を復元する",value:"RestorePreviousState"},{text:"常に表示する",value:"AlwaysDisplay"},{text:"常に折りたたむ",value:"AlwaysFold"}],tv_panel_active_tab:[{text:"番組情報タブ",value:"Program"},{text:"チャンネルタブ",value:"Channel"},{text:"コメントタブ",value:"Comment"},{text:"Twitter タブ",value:"Twitter"}],import_settings_file:null}},computed:Object.assign({},(0,a.Kc)(st)),created(){!0===this.settingsStore.settings.tv_data_saver_mode&&(this.tv_streaming_quality=Re)},watch:{"settingsStore.settings.tv_data_saver_mode":{immediate:!0,handler(t){this.tv_streaming_quality=!0===t?Re:Ve}}},methods:{exportSettings(){const t=JSON.stringify(this.settingsStore.settings,null,4),e=new Blob([t],{type:"application/json"});ht.downloadBlobData(e,"KonomiTV-Settings.json"),this.$message.success("設定をエクスポートしました。")},async importSettings(){if(null===this.import_settings_file)return void this.$message.error("インポートする設定データを選択してください!");const t=await this.settingsStore.importClientSettings(this.import_settings_file);!0===t?(this.$message.success("設定をインポートしました。"),window.setTimeout((()=>this.$router.go(0)),300)):this.$message.error("設定データが不正なため、インポートできませんでした。")},async resetSettings(){await this.settingsStore.resetClientSettings(),this.$message.success("設定をリセットしました。"),window.setTimeout((()=>this.$router.go(0)),300)}}}),Ge=Fe,We=(0,h.Z)(Ge,Me,Ue,!1,null,null,null),qe=We.exports,Xe=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"route-container"},[e("Header"),e("main",[e("Navigation"),e(pt.Z,{staticClass:"settings-container d-flex px-5 py-5 mx-auto background",attrs:{elevation:"0",width:"100%","max-width":"1000"}},[e(ve.Z,{staticClass:"settings-navigation flex-shrink-0 background",attrs:{permanent:"",width:"100%",height:"auto"}},[e(he.Z,{staticClass:"px-1"},[e(pe.km,[e("h1",[t._v("設定")])])],1),e(me.Z,{staticClass:"mt-2 px-0",attrs:{nav:""}},[e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/general"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{staticStyle:{padding:"0 3px"},attrs:{icon:"fa-solid:sliders-h",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("全般")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/caption"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{attrs:{icon:"fluent:subtitles-16-filled",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("字幕")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/capture"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{attrs:{icon:"fluent:image-multiple-16-filled",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("キャプチャ")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/account"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{attrs:{icon:"fluent:person-20-filled",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("アカウント")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/jikkyo"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{staticStyle:{padding:"0 2px"},attrs:{icon:"bi:chat-left-text-fill",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("ニコニコ実況")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/twitter"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{staticStyle:{padding:"0 1px"},attrs:{icon:"fa-brands:twitter",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("Twitter")])],1)],1),e(he.Z,{staticClass:"px-4",attrs:{link:"",color:"primary",to:"/settings/server"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{attrs:{icon:"fluent:server-surface-16-filled",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v("サーバー設定")])],1)],1),e(he.Z,{staticClass:"px-4 settings-navigation-version",class:{"settings-navigation-version--highlight":t.versionStore.is_update_available},attrs:{link:"",color:"primary",href:"https://github.com/tsukumijima/KonomiTV"}},[e(ge.Z,{staticClass:"mr-4"},[e("Icon",{attrs:{icon:"fluent:info-16-regular",width:"26px"}})],1),e(pe.km,[e(pe.V9,[t._v(" version "+t._s(t.versionStore.client_version)+t._s(t.versionStore.is_update_available?" (Update Available)":"")+" ")])],1)],1)],1)],1)],1)],1)],1)},Ye=[],Je=o["default"].extend({name:"Settings-Index",components:{Header:It,Navigation:Rt},computed:Object.assign({},(0,a.Kc)(Ht)),async created(){await this.versionStore.fetchServerVersion()}}),Qe=Je,ts=(0,h.Z)(Qe,Xe,Ye,!1,null,"48d089f3",null),es=ts.exports,ss=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("SettingsBase",[e("h2",{staticClass:"settings__heading"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"settings__back-button",attrs:{to:"/settings/"}},[e("Icon",{attrs:{icon:"fluent:arrow-left-12-filled",width:"25px"}})],1),e("Icon",{attrs:{icon:"bi:chat-left-text-fill",width:"19px"}}),e("span",{staticClass:"ml-3"},[t._v("ニコニコ実況")])],1),e("div",{staticClass:"settings__content",class:{"settings__content--loading":t.is_loading}},[null===t.userStore.user||null===t.userStore.user.niconico_user_id?e("div",{staticClass:"niconico-account niconico-account--anonymous"},[e("div",{staticClass:"niconico-account-wrapper"},[e("Icon",{staticClass:"flex-shrink-0",attrs:{icon:"bi:chat-left-text-fill",width:"45px"}}),e("div",{staticClass:"niconico-account__info ml-4"},[e("div",{staticClass:"niconico-account__info-name"},[e("span",{staticClass:"niconico-account__info-name-text"},[t._v("ニコニコアカウントと連携していません")])]),e("span",{staticClass:"niconico-account__info-description"},[t._v(" ニコニコアカウントと連携すると、テレビを見ながらニコニコ実況にコメントできるようになります。 ")])])],1),e(j.Z,{staticClass:"niconico-account__login ml-auto",attrs:{color:"secondary",width:"130",height:"56",depressed:""},on:{click:function(e){return t.loginNiconicoAccount()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:plug-connected-20-filled",height:"26"}}),t._v("連携する ")],1)],1):t._e(),null!==t.userStore.user&&null!==t.userStore.user.niconico_user_id?e("div",{staticClass:"niconico-account"},[e("div",{staticClass:"niconico-account-wrapper"},[e("img",{staticClass:"niconico-account__icon",attrs:{src:t.userStore.user_niconico_icon_url}}),e("div",{staticClass:"niconico-account__info"},[e("div",{staticClass:"niconico-account__info-name"},[e("span",{staticClass:"niconico-account__info-name-text"},[t._v(t._s(t.userStore.user.niconico_user_name)+" と連携しています")])]),e("span",{staticClass:"niconico-account__info-description"},[e("span",{staticClass:"mr-2",staticStyle:{"white-space":"nowrap"}},[t._v("Niconico User ID:")]),e("a",{staticClass:"mr-2",attrs:{href:`https://www.nicovideo.jp/user/${t.userStore.user.niconico_user_id}`,target:"_blank"}},[t._v(t._s(t.userStore.user.niconico_user_id))]),1==t.userStore.user.niconico_user_premium?e("span",{staticClass:"secondary--text"},[t._v("(Premium)")]):t._e()])])]),e(j.Z,{staticClass:"niconico-account__login ml-auto",attrs:{color:"secondary",width:"130",height:"56",depressed:""},on:{click:function(e){return t.logoutNiconicoAccount()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:plug-disconnected-20-filled",height:"26"}}),t._v("連携解除 ")],1)],1):t._e(),e("div",{staticClass:"settings__item mt-7"},[e("div",{staticClass:"settings__item-heading"},[t._v("コメントのミュート設定")]),e("div",{staticClass:"settings__item-label"},[t._v(" 表示したくないコメントを、映像上やコメントリストに表示しないようにミュートできます。"),e("br")])]),e(j.Z,{staticClass:"settings__save-button mt-4",attrs:{depressed:""},on:{click:function(e){t.comment_mute_settings_modal=!t.comment_mute_settings_modal}}},[e("Icon",{attrs:{icon:"heroicons-solid:filter",height:"19px"}}),e("span",{staticClass:"ml-1"},[t._v("コメントのミュート設定を開く")])],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("コメントの速さ")]),e("div",{staticClass:"settings__item-label"},[t._v(" プレイヤーに流れるコメントの速さを設定します。"),e("br"),t._v(" たとえば 1.2 に設定すると、コメントが 1.2 倍速く流れます。"),e("br")]),e(Ie.Z,{staticClass:"settings__item-form",attrs:{ticks:"always","thumb-label":"","hide-details":"",step:.1,min:.5,max:2},model:{value:t.settingsStore.settings.comment_speed_rate,callback:function(e){t.$set(t.settingsStore.settings,"comment_speed_rate",e)},expression:"settingsStore.settings.comment_speed_rate"}})],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("コメントの文字サイズ")]),e("div",{staticClass:"settings__item-label"},[t._v(" プレイヤーに流れるコメントの文字サイズの基準値を設定します。"),e("br"),t._v(" 実際の文字サイズは画面サイズに合わせて調整されます。デフォルトの文字サイズは 34px です。"),e("br")]),e(Ie.Z,{staticClass:"settings__item-form",attrs:{ticks:"always","thumb-label":"","hide-details":"",min:20,max:60},model:{value:t.settingsStore.settings.comment_font_size,callback:function(e){t.$set(t.settingsStore.settings,"comment_font_size",e)},expression:"settingsStore.settings.comment_font_size"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"close_comment_form_after_sending"}},[t._v("コメント送信後にコメント入力フォームを閉じる")]),e("label",{staticClass:"settings__item-label",attrs:{for:"close_comment_form_after_sending"}},[t._v(" この設定をオンにすると、コメントを送信した後に、コメント入力フォームが自動で閉じるようになります。"),e("br"),t._v(" コメント入力フォームが表示されたままだと、大半のショートカットキーが文字入力と競合して使えなくなります。とくに理由がなければ、オンにしておくのがおすすめです。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"close_comment_form_after_sending",inset:"","hide-details":""},model:{value:t.settingsStore.settings.close_comment_form_after_sending,callback:function(e){t.$set(t.settingsStore.settings,"close_comment_form_after_sending",e)},expression:"settingsStore.settings.close_comment_form_after_sending"}})],1)],1),e("CommentMuteSettings",{model:{value:t.comment_mute_settings_modal,callback:function(e){t.comment_mute_settings_modal=e},expression:"comment_mute_settings_modal"}})],1)},is=[],as=function(){var t=this,e=t._self._c;t._self._setupProxy;return e(le.Z,{attrs:{"max-width":"770",transition:"slide-y-transition"},model:{value:t.comment_mute_settings_modal,callback:function(e){t.comment_mute_settings_modal=e},expression:"comment_mute_settings_modal"}},[e(pt.Z,{staticClass:"comment-mute-settings"},[e(gt.EB,{staticClass:"px-5 pt-5 pb-3 d-flex align-center font-weight-bold",staticStyle:{height:"60px"}},[e("Icon",{attrs:{icon:"heroicons-solid:filter",height:"26px"}}),e("span",{staticClass:"ml-3"},[t._v("コメントのミュート設定")]),e(kt.Z),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"d-flex align-center rounded-circle cursor-pointer px-2 py-2",on:{click:function(e){t.comment_mute_settings_modal=!1}}},[e("Icon",{attrs:{icon:"fluent:dismiss-12-filled",width:"23px",height:"23px"}})],1)],1),e("div",{staticClass:"px-5 pb-5"},[e("div",{staticClass:"text-subtitle-1 d-flex align-center font-weight-bold mt-4"},[e("Icon",{attrs:{icon:"fa-solid:sliders-h",width:"24px",height:"20px"}}),e("span",{staticClass:"ml-2"},[t._v("クイック設定")])],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"mute_vulgar_comments"}},[t._v(" 露骨な表現を含むコメントをミュートする ")]),e("label",{staticClass:"settings__item-label",attrs:{for:"mute_vulgar_comments"}},[t._v(" 性的な単語などの露骨・下品な表現を含むコメントを、一括でミュートするかを設定します。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"mute_vulgar_comments",inset:"","hide-details":""},model:{value:t.settingsStore.settings.mute_vulgar_comments,callback:function(e){t.$set(t.settingsStore.settings,"mute_vulgar_comments",e)},expression:"settingsStore.settings.mute_vulgar_comments"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"mute_abusive_discriminatory_prejudiced_comments"}},[t._v(" ネガティブな表現、差別的な表現、政治的に偏った表現を含むコメントをミュートする ")]),e("label",{staticClass:"settings__item-label",attrs:{for:"mute_abusive_discriminatory_prejudiced_comments"}},[t._v(" 『死ね』『殺す』などのネガティブな表現、特定の国や人々への差別的な表現、政治的に偏った表現を含むコメントを、一括でミュートするかを設定します。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"mute_abusive_discriminatory_prejudiced_comments",inset:"","hide-details":""},model:{value:t.settingsStore.settings.mute_abusive_discriminatory_prejudiced_comments,callback:function(e){t.$set(t.settingsStore.settings,"mute_abusive_discriminatory_prejudiced_comments",e)},expression:"settingsStore.settings.mute_abusive_discriminatory_prejudiced_comments"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"mute_big_size_comments"}},[t._v(" 文字サイズが大きいコメントをミュートする ")]),e("label",{staticClass:"settings__item-label",attrs:{for:"mute_big_size_comments"}},[t._v(" 通常より大きい文字サイズで表示されるコメントを、一括でミュートするかを設定します。"),e("br"),t._v(" 文字サイズが大きいコメントには迷惑なコメントが多いです。基本的にはオンにしておくのがおすすめです。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"mute_big_size_comments",inset:"","hide-details":""},model:{value:t.settingsStore.settings.mute_big_size_comments,callback:function(e){t.$set(t.settingsStore.settings,"mute_big_size_comments",e)},expression:"settingsStore.settings.mute_big_size_comments"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"mute_fixed_comments"}},[t._v(" 映像の上下に固定表示されるコメントをミュートする ")]),e("label",{staticClass:"settings__item-label",attrs:{for:"mute_fixed_comments"}},[t._v(" 映像の上下に固定された状態で表示されるコメントを、一括でミュートするかを設定します。"),e("br"),t._v(" 固定表示されるコメントが煩わしい方におすすめです。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"mute_fixed_comments",inset:"","hide-details":""},model:{value:t.settingsStore.settings.mute_fixed_comments,callback:function(e){t.$set(t.settingsStore.settings,"mute_fixed_comments",e)},expression:"settingsStore.settings.mute_fixed_comments"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"mute_colored_comments"}},[t._v(" 色付きのコメントをミュートする ")]),e("label",{staticClass:"settings__item-label",attrs:{for:"mute_colored_comments"}},[t._v(" 白以外の色で表示される色付きのコメントを、一括でミュートするかを設定します。"),e("br"),t._v(" この設定をオンにしておくと、目立つ色のコメントを一掃できます。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"mute_colored_comments",inset:"","hide-details":""},model:{value:t.settingsStore.settings.mute_colored_comments,callback:function(e){t.$set(t.settingsStore.settings,"mute_colored_comments",e)},expression:"settingsStore.settings.mute_colored_comments"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"mute_consecutive_same_characters_comments"}},[t._v(" 8文字以上同じ文字が連続しているコメントをミュートする ")]),e("label",{staticClass:"settings__item-label",attrs:{for:"mute_consecutive_same_characters_comments"}},[t._v(" 『wwwwwwwwwww』『あばばばばばばばばば』など、8文字以上同じ文字が連続しているコメントを、一括でミュートするかを設定します。"),e("br"),t._v(" しばしばあるテンプレコメントが煩わしい方におすすめです。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"mute_consecutive_same_characters_comments",inset:"","hide-details":""},model:{value:t.settingsStore.settings.mute_consecutive_same_characters_comments,callback:function(e){t.$set(t.settingsStore.settings,"mute_consecutive_same_characters_comments",e)},expression:"settingsStore.settings.mute_consecutive_same_characters_comments"}})],1),e("div",{staticClass:"text-subtitle-1 d-flex align-center font-weight-bold mt-4"},[e("Icon",{attrs:{icon:"fluent:comment-dismiss-20-filled",width:"24px"}}),e("span",{staticClass:"ml-2 mr-2"},[t._v("ミュート済みのキーワード")]),e(j.Z,{staticClass:"ml-auto",attrs:{depressed:""},on:{click:function(e){return t.settingsStore.settings.muted_comment_keywords.push({match:"partial",pattern:""})}}},[e("Icon",{attrs:{icon:"fluent:add-12-filled",height:"17px"}}),e("span",{staticClass:"ml-1"},[t._v("追加")])],1)],1),e("div",{staticClass:"muted-comment-items"},t._l(t.settingsStore.settings.muted_comment_keywords,(function(s,i){return e("div",{key:i,staticClass:"muted-comment-item"},[e(yt.Z,{staticClass:"muted-comment-item__input",attrs:{type:"search",dense:"",outlined:"","hide-details":"",placeholder:"ミュートするキーワードを入力"},model:{value:t.settingsStore.settings.muted_comment_keywords[i].pattern,callback:function(e){t.$set(t.settingsStore.settings.muted_comment_keywords[i],"pattern",e)},expression:"settingsStore.settings.muted_comment_keywords[index].pattern"}}),e(je.Z,{staticClass:"muted-comment-item__match-type",attrs:{dense:"",outlined:"","hide-details":"",items:t.muted_comment_keyword_match_type},model:{value:t.settingsStore.settings.muted_comment_keywords[i].match,callback:function(e){t.$set(t.settingsStore.settings.muted_comment_keywords[i],"match",e)},expression:"settingsStore.settings.muted_comment_keywords[index].match"}}),e("button",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"muted-comment-item__delete-button",on:{click:function(e){t.settingsStore.settings.muted_comment_keywords.splice(t.settingsStore.settings.muted_comment_keywords.indexOf(s),1)}}},[e("Icon",{attrs:{icon:"fluent:delete-16-filled",width:"20px"}})],1)],1)})),0),e("div",{staticClass:"text-subtitle-1 d-flex align-center font-weight-bold mt-4"},[e("Icon",{attrs:{icon:"fluent:person-prohibited-20-filled",width:"24px"}}),e("span",{staticClass:"ml-2 mr-2"},[t._v("ミュート済みのニコニコユーザー ID")]),e(j.Z,{staticClass:"ml-auto",attrs:{depressed:""},on:{click:function(e){return t.settingsStore.settings.muted_niconico_user_ids.push("")}}},[e("Icon",{attrs:{icon:"fluent:add-12-filled",height:"17px"}}),e("span",{staticClass:"ml-1"},[t._v("追加")])],1)],1),e("div",{staticClass:"muted-comment-items"},t._l(t.settingsStore.settings.muted_niconico_user_ids,(function(s,i){return e("div",{key:i,staticClass:"muted-comment-item"},[e(yt.Z,{staticClass:"muted-comment-item__input",attrs:{type:"search",dense:"",outlined:"","hide-details":"",placeholder:"ミュートするニコニコユーザー ID を入力"},model:{value:t.settingsStore.settings.muted_niconico_user_ids[i],callback:function(e){t.$set(t.settingsStore.settings.muted_niconico_user_ids,i,e)},expression:"settingsStore.settings.muted_niconico_user_ids[index]"}}),e("button",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"muted-comment-item__delete-button",on:{click:function(e){t.settingsStore.settings.muted_niconico_user_ids.splice(t.settingsStore.settings.muted_niconico_user_ids.indexOf(s),1)}}},[e("Icon",{attrs:{icon:"fluent:delete-16-filled",width:"20px"}})],1)],1)})),0)])],1)],1)},ns=[],rs=o["default"].extend({name:"CommentMuteSettings",model:{prop:"showing",event:"change"},props:{showing:{type:Boolean,required:!0}},data(){return{interval_timer_id:0,comment_mute_settings_modal:!1,muted_comment_keyword_match_type:[{text:"部分一致",value:"partial"},{text:"前方一致",value:"forward"},{text:"後方一致",value:"backward"},{text:"完全一致",value:"exact"},{text:"正規表現",value:"regex"}]}},computed:Object.assign({},(0,a.Kc)(st)),watch:{showing(){this.comment_mute_settings_modal=this.showing},comment_mute_settings_modal(){this.$emit("change",this.comment_mute_settings_modal)}}}),os=rs,ls=(0,h.Z)(os,as,ns,!1,null,"2cd59ba0",null),cs=ls.exports;class _s{static async fetchAuthorizationURL(){const t=await G.get("/niconico/auth");return"is_error"in t?(G.showGenericError(t,"ニコニコアカウントとの連携用の認証 URL を取得できませんでした。"),null):t.data.authorization_url}static async logoutAccount(){const t=await G["delete"]("/niconico/logout");return!("is_error"in t)||(G.showGenericError(t,"ニコニコアカウントとの連携を解除できませんでした。"),!1)}}var ds=_s,us=o["default"].extend({name:"Settings-Jikkyo",components:{SettingsBase:ke,CommentMuteSettings:cs},data(){return{comment_mute_settings_modal:!1,is_loading:!0}},computed:Object.assign({},(0,a.Kc)(st,R)),async created(){if(await this.userStore.fetchUser(),this.is_loading=!1,""!==location.hash){const t=new URLSearchParams(location.hash.replace("#",""));if(null!==t.get("status")&&null!==t.get("detail")){const e=parseInt(t.get("status")),s=t.get("detail");this.onOAuthCallbackReceived(e,s),history.replaceState(null,""," ")}}},methods:{async loginNiconicoAccount(){if(!1===this.userStore.is_logged_in)return void this.$message.warning("連携をはじめるには、KonomiTV アカウントにログインしてください。");const t=await ds.fetchAuthorizationURL();if(null===t)return;if(!0===ht.isMobileDevice())return void(location.href=t);const e=window.open(t,"KonomiTV-OAuthPopup",ht.getWindowFeatures());if(null===e)return void this.$message.error("ポップアップウインドウを開けませんでした。");const s=async t=>{if(e.closed)return;if("object"!==ht["typeof"](t.data))return;if("KonomiTV-OAuthPopup"in t.data===!1)return;e&&e.close(),window.removeEventListener("message",s);const i=t.data["KonomiTV-OAuthPopup"]["status"],a=t.data["KonomiTV-OAuthPopup"]["detail"];this.onOAuthCallbackReceived(i,a)};window.addEventListener("message",s)},async onOAuthCallbackReceived(t,e){if(console.log(`NiconicoAuthCallbackAPI: Status: ${t} / Detail: ${e}`),200===t)await this.userStore.fetchUser(!0),this.$message.success("ニコニコアカウントと連携しました。");else if(e.startsWith("Authorization was denied (access_denied)"))this.$message.error("ニコニコアカウントとの連携がキャンセルされました。");else if(e.startsWith("Failed to get access token (HTTP Error ")){const t=e.replace("Failed to get access token ","");this.$message.error(`アクセストークンの取得に失敗しました。${t}`)}else if(e.startsWith("Failed to get access token (Connection Timeout)"))this.$message.error("アクセストークンの取得に失敗しました。ニコニコで障害が発生している可能性があります。");else if(e.startsWith("Failed to get user information (HTTP Error ")){const t=e.replace("Failed to get user information ","");this.$message.error(`ニコニコアカウントのユーザー情報の取得に失敗しました。${t}`)}else e.startsWith("Failed to get user information (Connection Timeout)")?this.$message.error("ニコニコアカウントのユーザー情報の取得に失敗しました。ニコニコで障害が発生している可能性があります。"):this.$message.error(`ニコニコアカウントとの連携に失敗しました。(${e})`)},async logoutNiconicoAccount(){const t=await ds.logoutAccount();!1!==t&&(await this.userStore.fetchUser(!0),this.$message.success("ニコニコアカウントとの連携を解除しました。"))}}}),ms=us,hs=(0,h.Z)(ms,ss,is,!1,null,"ac48731c",null),ps=hs.exports,gs=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("SettingsBase",[e("h2",{staticClass:"settings__heading"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"settings__back-button",attrs:{to:"/settings/"}},[e("Icon",{attrs:{icon:"fluent:arrow-left-12-filled",width:"25px"}})],1),e("Icon",{attrs:{icon:"fluent:server-surface-16-filled",width:"22px"}}),e("span",{staticClass:"ml-2"},[t._v("サーバー設定")])],1),e("div",{staticClass:"settings__content"},[e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("鋭意開発中…")])])])])},vs=[],fs=o["default"].extend({name:"Settings-Server",components:{SettingsBase:ke}}),ws=fs,ys=(0,h.Z)(ws,gs,vs,!1,null,null,null),bs=ys.exports,Cs=s(8236),ks=s(6275),xs=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("SettingsBase",[e("h2",{staticClass:"settings__heading"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"settings__back-button",attrs:{to:"/settings/"}},[e("Icon",{attrs:{icon:"fluent:arrow-left-12-filled",width:"25px"}})],1),e("Icon",{attrs:{icon:"fa-brands:twitter",width:"22px"}}),e("span",{staticClass:"ml-3"},[t._v("Twitter")])],1),e("div",{staticClass:"settings__content",class:{"settings__content--loading":t.is_loading}},[e("div",{staticClass:"twitter-accounts"},[null!==t.userStore.user&&t.userStore.user.twitter_accounts.length>0?e("div",{staticClass:"twitter-accounts__heading"},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:person-board-20-filled",height:"30"}}),t._v("連携中のアカウント ")],1):t._e(),null===t.userStore.user||0===t.userStore.user.twitter_accounts.length?e("div",{staticClass:"twitter-accounts__guide"},[e("Icon",{staticClass:"flex-shrink-0",attrs:{icon:"fa-brands:twitter",width:"45px"}}),e("div",{staticClass:"ml-4"},[e("div",{staticClass:"font-weight-bold text-h6"},[t._v("Twitter アカウントと連携していません")]),e("div",{staticClass:"text--text text--darken-1 text-subtitle-2 mt-1"},[t._v(" Twitter アカウントと連携すると、テレビを見ながら Twitter にツイートしたり、ほかの実況ツイートをリアルタイムで表示できるようになります。 ")])])],1):t._e(),t._l(null!==t.userStore.user?t.userStore.user.twitter_accounts:[],(function(s){return e("div",{key:s.id,staticClass:"twitter-account"},[e("img",{staticClass:"twitter-account__icon",attrs:{src:s.icon_url}}),e("div",{staticClass:"twitter-account__info"},[e("div",{staticClass:"twitter-account__info-name"},[e("span",{staticClass:"twitter-account__info-name-text"},[t._v(t._s(s.name))])]),e("span",{staticClass:"twitter-account__info-screen-name"},[t._v(" @"+t._s(s.screen_name)+" "),!0===s.is_oauth_session?e("span",[t._v("(Legacy Session)")]):t._e()])]),e(j.Z,{staticClass:"twitter-account__logout ml-auto",attrs:{width:"124",height:"52",depressed:""},on:{click:function(e){return t.logoutTwitterAccount(s.screen_name)}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:plug-disconnected-20-filled",height:"24"}}),t._v("連携解除 ")],1)],1)})),e(j.Z,{staticClass:"twitter-account__login",attrs:{color:"secondary","max-width":"250",height:"50",depressed:""},on:{click:function(e){return t.loginTwitterAccountWithPasswordForm()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:plug-connected-20-filled",height:"24"}}),t._v("連携するアカウントを追加 ")],1),e(le.Z,{attrs:{"max-width":"600"},model:{value:t.twitter_password_auth_dialog,callback:function(e){t.twitter_password_auth_dialog=e},expression:"twitter_password_auth_dialog"}},[e(pt.Z,[e(gt.EB,{staticClass:"justify-center pt-6 font-weight-bold"},[t._v("Twitter にログイン")]),e(gt.ZB,{staticClass:"pt-2 pb-0"},[e("p",{staticClass:"mb-1"},[t._v("2023/4/30 以降、Twitter のサードパーティー API の事実上の廃止により、従来のアプリ連携では Twitter にアクセスできなくなりました。")]),e("p",{staticClass:"mb-1"},[t._v("そこで KonomiTV では、代わりにユーザー名とパスワードでログインすることで、これまで通り Twitter 連携ができるようにしています (2要素認証を設定しているアカウントには対応していません) 。")]),e("p",{staticClass:"mb-1"},[t._v("万全は期していますが、非公式な方法のため、使い方次第ではアカウントにペナルティが適用される可能性もあります。自己の責任のもとでご利用ください。")]),e(ft.Z,{ref:"twitter_form",staticClass:"settings__item",on:{submit:function(t){t.preventDefault()}}},[e(yt.Z,{ref:"twitter_screen_name",staticClass:"settings__item-form mt-6",attrs:{outlined:"",label:"ユーザー名 (@ から始まる ID)",placeholder:"screen_name",dense:t.is_form_dense,rules:[t=>!!t||"ユーザー名を入力してください。"]},model:{value:t.twitter_screen_name,callback:function(e){t.twitter_screen_name=e},expression:"twitter_screen_name"}}),e(yt.Z,{staticClass:"settings__item-form",attrs:{outlined:"",label:"パスワード",dense:t.is_form_dense,type:t.twitter_password_showing?"text":"password","append-icon":t.twitter_password_showing?"mdi-eye":"mdi-eye-off",rules:[t=>!!t||"パスワードを入力してください。"]},on:{"click:append":function(e){t.twitter_password_showing=!t.twitter_password_showing}},model:{value:t.twitter_password,callback:function(e){t.twitter_password=e},expression:"twitter_password"}})],1)],1),e(gt.h7,{staticClass:"pt-0 px-6 pb-5"},[e(kt.Z),e(j.Z,{attrs:{color:"text",height:"40",text:""},on:{click:function(e){t.twitter_password_auth_dialog=!1}}},[t._v("キャンセル")]),e(j.Z,{staticClass:"px-4",attrs:{color:"secondary",height:"40"},on:{click:function(e){return t.loginTwitterAccountWithPassword()}}},[t._v("ログイン")])],1)],1)],1),e(j.Z,{staticClass:"twitter-account__login",attrs:{color:"secondary","max-width":"310",height:"50",depressed:""},on:{click:function(e){return t.loginTwitterAccountWithOAuth()}}},[e("Icon",{staticClass:"mr-2",attrs:{icon:"fluent:plug-connected-20-filled",height:"24"}}),t._v("連携するアカウントを追加 (Legacy) ")],1)],2),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"fold_panel_after_sending_tweet"}},[t._v("ツイート送信後にパネルを折りたたむ")]),e("label",{staticClass:"settings__item-label",attrs:{for:"fold_panel_after_sending_tweet"}},[t._v(" この設定をオンにすると、ツイートを送信した後に、パネルが自動で折りたたまれます。"),e("br"),t._v(" ツイートするとき以外はできるだけ映像を大きくして見たい方におすすめです。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"fold_panel_after_sending_tweet",inset:"","hide-details":""},model:{value:t.settingsStore.settings.fold_panel_after_sending_tweet,callback:function(e){t.$set(t.settingsStore.settings,"fold_panel_after_sending_tweet",e)},expression:"settingsStore.settings.fold_panel_after_sending_tweet"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"reset_hashtag_when_program_switches"}},[t._v("番組が切り替わったときにハッシュタグフォームをリセットする")]),e("label",{staticClass:"settings__item-label",attrs:{for:"reset_hashtag_when_program_switches"}},[t._v(" チャンネルを切り替えたときや、視聴中の番組が終了し次の番組の放送が開始されたときに、ハッシュタグフォームをリセットするかを設定します。"),e("br"),t._v(" この設定をオンにしておけば、「誤って前番組のハッシュタグをつけたまま次番組の実況ツイートをしてしまう」といったミスを回避できます。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"reset_hashtag_when_program_switches",inset:"","hide-details":""},model:{value:t.settingsStore.settings.reset_hashtag_when_program_switches,callback:function(e){t.$set(t.settingsStore.settings,"reset_hashtag_when_program_switches",e)},expression:"settingsStore.settings.reset_hashtag_when_program_switches"}})],1),e("div",{staticClass:"settings__item settings__item--switch"},[e("label",{staticClass:"settings__item-heading",attrs:{for:"auto_add_watching_channel_hashtag"}},[t._v("視聴中のチャンネルに対応する局タグを自動で追加する")]),e("label",{staticClass:"settings__item-label",attrs:{for:"auto_add_watching_channel_hashtag"}},[t._v(" この設定をオンにすると、視聴中のチャンネルに対応する局タグ (#nhk, #tokyomx など) がハッシュタグフォームに自動で追加されます。"),e("br"),t._v(" 現時点で、局タグは三大首都圏の地上波・BS の一部チャンネル・AT-X にのみ対応しています。"),e("br")]),e(_e.Z,{staticClass:"settings__item-switch",attrs:{id:"auto_add_watching_channel_hashtag",inset:"","hide-details":""},model:{value:t.settingsStore.settings.auto_add_watching_channel_hashtag,callback:function(e){t.$set(t.settingsStore.settings,"auto_add_watching_channel_hashtag",e)},expression:"settingsStore.settings.auto_add_watching_channel_hashtag"}})],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("デフォルトで表示される Twitter タブ内のタブ")]),e("div",{staticClass:"settings__item-label"},[t._v(" 視聴画面を開いたときに、パネルの Twitter タブの中で最初に表示されるタブを設定します。"),e("br")]),e(je.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",dense:t.is_form_dense,items:t.twitter_active_tab},model:{value:t.settingsStore.settings.twitter_active_tab,callback:function(e){t.$set(t.settingsStore.settings,"twitter_active_tab",e)},expression:"settingsStore.settings.twitter_active_tab"}})],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("ツイートにつけるハッシュタグの位置")]),e("div",{staticClass:"settings__item-label"},[t._v(" ツイート本文から見て、ハッシュタグをどの位置につけてツイートするかを設定します。"),e("br")]),e(je.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",dense:t.is_form_dense,items:t.tweet_hashtag_position},model:{value:t.settingsStore.settings.tweet_hashtag_position,callback:function(e){t.$set(t.settingsStore.settings,"tweet_hashtag_position",e)},expression:"settingsStore.settings.tweet_hashtag_position"}})],1),e("div",{staticClass:"settings__item"},[e("div",{staticClass:"settings__item-heading"},[t._v("ツイートするキャプチャに番組タイトルの透かしを描画する")]),e("div",{staticClass:"settings__item-label"},[t._v(" ツイートするキャプチャに、透かしとして視聴中の番組タイトルを描画するかを設定します。"),e("br"),t._v(" 透かしの描画位置は 左上・右上・左下・右下 から選択できます。"),e("br")]),e(je.Z,{staticClass:"settings__item-form",attrs:{outlined:"","hide-details":"",dense:t.is_form_dense,items:t.tweet_capture_watermark_position},model:{value:t.settingsStore.settings.tweet_capture_watermark_position,callback:function(e){t.$set(t.settingsStore.settings,"tweet_capture_watermark_position",e)},expression:"settingsStore.settings.tweet_capture_watermark_position"}})],1)]),e(Cs.Z,{attrs:{value:t.is_twitter_password_auth_sending,"z-index":"300"}},[e(ks.Z,{attrs:{color:"secondary",indeterminate:"",size:"64"}})],1)],1)},Ss=[];class Os{static async fetchAuthorizationURL(){const t=await G.get("/twitter/auth");return"is_error"in t?(G.showGenericError(t,"Twitter アカウントとの連携用の認証 URL を取得できませんでした。"),null):t.data.authorization_url}static async authWithPassword(t){const e=await G.post("/twitter/password-auth",t);if("is_error"in e){if(e.error.message.startsWith("Failed to authenticate with password")){const t=e.error.message.match(/Message: (.+)\)/)[1];K.error(`ログインに失敗しました。${t}`)}else if(e.error.message.startsWith("Unexpected error occurred while authenticate with password")){const t=e.error.message.match(/Message: (.+)\)/)[1];K.error(`ログインフローの途中で予期せぬエラーが発生しました。${t}`)}else e.error.message.startsWith("Failed to get user information")?K.error("Twitter アカウントのユーザー情報の取得に失敗しました。"):G.showGenericError(e,"Twitter アカウントとの連携に失敗しました。");return!1}return!0}static async logoutAccount(t){const e=await G["delete"](`/twitter/accounts/${t}`);return!("is_error"in e)||(G.showGenericError(e,"Twitter アカウントとの連携を解除できませんでした。"),!1)}static async sendTweet(t,e,s){const i=new FormData;i.append("tweet",e);for(const n of s)i.append("images",n);const a=await G.post(`/twitter/accounts/${t}/tweets`,i,{headers:{"Content-Type":"multipart/form-data"}});return"is_error"in a?a.error.message?Number.isNaN(a.status)?{message:`エラー: ツイートの送信に失敗しました。(${a.error.message})`,is_error:!0}:{message:`エラー: ツイートの送信に失敗しました。(HTTP Error ${a.status} / ${a.error.message})`,is_error:!0}:{message:`エラー: ツイートの送信に失敗しました。(HTTP Error ${a.status})`,is_error:!0}:!0===a.data.is_success?{message:a.data.detail,is_error:!1}:{message:`エラー: ${a.data.detail}`,is_error:!0}}}var Ts=Os,js=o["default"].extend({name:"Settings-Twitter",components:{SettingsBase:ke},data(){return{is_form_dense:ht.isSmartphoneHorizontal(),twitter_active_tab:[{text:"ツイート検索タブ",value:"Search"},{text:"タイムラインタブ",value:"Timeline"},{text:"キャプチャタブ",value:"Capture"}],tweet_hashtag_position:[{text:"ツイート本文の前に追加する",value:"Prepend"},{text:"ツイート本文の後に追加する",value:"Append"},{text:"ツイート本文の前に追加してから改行する",value:"PrependWithLineBreak"},{text:"ツイート本文の後に改行してから追加する",value:"AppendWithLineBreak"}],tweet_capture_watermark_position:[{text:"透かしを描画しない",value:"None"},{text:"透かしをキャプチャの左上に描画する",value:"TopLeft"},{text:"透かしをキャプチャの右上に描画する",value:"TopRight"},{text:"透かしをキャプチャの左下に描画する",value:"BottomLeft"},{text:"透かしをキャプチャの右下に描画する",value:"BottomRight"}],is_loading:!0,is_twitter_password_auth_sending:!1,twitter_password_auth_dialog:!1,twitter_screen_name:"",twitter_password:"",twitter_password_showing:!1}},computed:Object.assign({},(0,a.Kc)(st,R)),async created(){if(await this.userStore.fetchUser(),this.is_loading=!1,""!==location.hash){const t=new URLSearchParams(location.hash.replace("#",""));if(null!==t.get("status")&&null!==t.get("detail")){const e=parseInt(t.get("status")),s=t.get("detail");this.onOAuthCallbackReceived(e,s),history.replaceState(null,""," ")}}},methods:{async loginTwitterAccountWithPasswordForm(){if(!1===this.userStore.is_logged_in)return this.$message.warning("連携をはじめるには、KonomiTV アカウントにログインしてください。"),await ht.sleep(.01),void(this.twitter_password_auth_dialog=!1);this.twitter_password_auth_dialog=!0},async loginTwitterAccountWithPassword(){if(!1===this.$refs.twitter_form.validate())return;this.is_twitter_password_auth_sending=!0;const t=await Ts.authWithPassword({screen_name:this.twitter_screen_name,password:this.twitter_password});if(this.is_twitter_password_auth_sending=!1,!1===t)return;if(await this.userStore.fetchUser(!0),null===this.userStore.user)return void this.$message.error("アカウント情報を取得できませんでした。");const e=[...this.userStore.user.twitter_accounts].sort(((t,e)=>t.updated_ate.updated_at?-1:0))[0];this.$message.success(`Twitter @${e.screen_name} と連携しました。`),this.$refs.twitter_form.reset(),this.twitter_password_auth_dialog=!1},async loginTwitterAccountWithOAuth(){if(!1===this.userStore.is_logged_in)return void this.$message.warning("連携をはじめるには、KonomiTV アカウントにログインしてください。");const t=await Ts.fetchAuthorizationURL();if(null===t)return;if(!0===ht.isMobileDevice())return void(location.href=t);const e=window.open(t,"KonomiTV-OAuthPopup",ht.getWindowFeatures());if(null===e)return void this.$message.error("ポップアップウインドウを開けませんでした。");const s=async t=>{if(e.closed)return;if("object"!==ht["typeof"](t.data))return;if("KonomiTV-OAuthPopup"in t.data===!1)return;e&&e.close(),window.removeEventListener("message",s);const i=t.data["KonomiTV-OAuthPopup"]["status"],a=t.data["KonomiTV-OAuthPopup"]["detail"];this.onOAuthCallbackReceived(i,a)};window.addEventListener("message",s)},async onOAuthCallbackReceived(t,e){if(console.log(`TwitterAuthCallbackAPI: Status: ${t} / Detail: ${e}`),200!==t)return void(e.startsWith("Authorization was denied by user")?this.$message.error("Twitter アカウントとの連携がキャンセルされました。"):e.startsWith("Failed to get access token")?this.$message.error("アクセストークンの取得に失敗しました。"):e.startsWith("Failed to get user information")?this.$message.error("Twitter アカウントのユーザー情報の取得に失敗しました。"):this.$message.error(`Twitter アカウントとの連携に失敗しました。(${e})`));if(await this.userStore.fetchUser(!0),null===this.userStore.user)return void this.$message.error("アカウント情報を取得できませんでした。");const s=[...this.userStore.user.twitter_accounts].sort(((t,e)=>t.updated_ate.updated_at?-1:0))[0];this.$message.success(`Twitter @${s.screen_name} と連携しました。`)},async logoutTwitterAccount(t){const e=await Ts.logoutAccount(t);!1!==e&&(await this.userStore.fetchUser(!0),this.$message.success(`Twitter @${t} との連携を解除しました。`))}}}),Is=js,Ps=(0,h.Z)(Is,xs,Ss,!1,null,"ea90430c",null),Zs=Ps.exports,zs=s(3333),$s=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"route-container"},[e("Header"),e("main",[e("Navigation"),e("div",{staticClass:"channels-container channels-container--home",class:{"channels-container--loading":t.is_loading}},[e("v-tabs-fix",{staticClass:"channels-tab",attrs:{centered:""},model:{value:t.tab,callback:function(e){t.tab=e},expression:"tab"}},t._l(Array.from(t.channelsStore.channels_list_with_pinned),(function([s]){return e(zs.Z,{key:s,staticClass:"channels-tab__item"},[t._v(" "+t._s(s)+" ")])})),1),e("v-tabs-items-fix",{staticClass:"channels-list",model:{value:t.tab,callback:function(e){t.tab=e},expression:"tab"}},t._l(Array.from(t.channelsStore.channels_list_with_pinned),(function([s,i]){return e("v-tab-item-fix",{key:s,staticClass:"channels-tabitem"},[e("div",{staticClass:"channels",class:`channels--tab-${s} channels--length-${i.length}`},[t._l(i,(function(s){return e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],key:s.id,staticClass:"channel",attrs:{to:`/tv/watch/${s.channel_id}`}},[e("div",{staticClass:"channel__broadcaster"},[e("img",{staticClass:"channel__broadcaster-icon",attrs:{src:`${t.Utils.api_base_url}/channels/${s.channel_id}/logo`}}),e("div",{staticClass:"channel__broadcaster-content"},[e("span",{staticClass:"channel__broadcaster-name"},[t._v("Ch: "+t._s(s.channel_number)+" "+t._s(s.channel_name))]),e("div",{staticClass:"channel__broadcaster-status"},[e("div",{staticClass:"channel__broadcaster-status-force",class:`channel__broadcaster-status-force--${t.ChannelUtils.getChannelForceType(s.channel_force)}`},[e("Icon",{attrs:{icon:"fa-solid:fire-alt",height:"12px"}}),e("span",{staticClass:"ml-1"},[t._v("勢い:")]),e("span",{staticClass:"ml-1"},[t._v(t._s(t.ProgramUtils.getAttribute(s,"channel_force","--")))]),e("span",{staticStyle:{"margin-left":"3px"}},[t._v(" コメ/分")])],1),e("div",{staticClass:"channel__broadcaster-status-viewers ml-4"},[e("Icon",{attrs:{icon:"fa-solid:eye",height:"14px"}}),e("span",{staticClass:"ml-1"},[t._v("視聴数:")]),e("span",{staticClass:"ml-1"},[t._v(t._s(s.viewers))])],1)])]),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip",value:t.isPinnedChannel(s.channel_id)?"ピン留めを外す":"ピン留めする",expression:"isPinnedChannel(channel.channel_id) ? 'ピン留めを外す' : 'ピン留めする'"}],staticClass:"channel__broadcaster-pin",class:{"channel__broadcaster-pin--pinned":t.isPinnedChannel(s.channel_id)},on:{click:function(e){e.preventDefault(),e.stopPropagation(),t.isPinnedChannel(s.channel_id)?t.removePinnedChannel(s.channel_id):t.addPinnedChannel(s.channel_id)},mousedown:function(t){t.preventDefault(),t.stopPropagation()}}},[e("Icon",{attrs:{icon:"fluent:pin-20-filled",width:"24px"}})],1)]),e("div",{staticClass:"channel__program-present"},[e("div",{staticClass:"channel__program-present-title-wrapper"},[e("span",{staticClass:"channel__program-present-title",domProps:{innerHTML:t._s(t.ProgramUtils.decorateProgramInfo(s.program_present,"title"))}}),e("span",{staticClass:"channel__program-present-time"},[t._v(t._s(t.ProgramUtils.getProgramTime(s.program_present)))])]),e("span",{staticClass:"channel__program-present-description",domProps:{innerHTML:t._s(t.ProgramUtils.decorateProgramInfo(s.program_present,"description"))}})]),e(kt.Z),e("div",{staticClass:"channel__program-following"},[e("div",{staticClass:"channel__program-following-title"},[e("span",{staticClass:"channel__program-following-title-decorate"},[t._v("NEXT")]),e("Icon",{staticClass:"channel__program-following-title-icon",attrs:{icon:"fluent:fast-forward-20-filled",width:"16px"}}),e("span",{staticClass:"channel__program-following-title-text",domProps:{innerHTML:t._s(t.ProgramUtils.decorateProgramInfo(s.program_following,"title"))}})],1),e("span",{staticClass:"channel__program-following-time"},[t._v(t._s(t.ProgramUtils.getProgramTime(s.program_following)))])]),e("div",{staticClass:"channel__progressbar"},[e("div",{staticClass:"channel__progressbar-progress",style:`width:${t.ProgramUtils.getProgramProgress(s.program_present)}%;`})])],1)})),"ピン留め"===s&&0===i.length?e("div",{staticClass:"pinned-container d-flex justify-center align-center w-100"},[e("div",{staticClass:"d-flex justify-center align-center flex-column"},[e("h2",[t._v("ピン留めされているチャンネルが"),e("br"),t._v("ありません。")]),e("div",{staticClass:"mt-4 text--text text--darken-1"},[t._v("各チャンネルの "),e("Icon",{staticStyle:{position:"relative",bottom:"-5px"},attrs:{icon:"fluent:pin-20-filled",width:"22px"}}),t._v(" アイコンから、よくみる"),e("br"),t._v("チャンネルをこのタブにピン留めできます。")],1),e("div",{staticClass:"mt-2 text--text text--darken-1"},[t._v("チャンネルをピン留めすると、"),e("br"),t._v("このタブが最初に表示されます。")])])]):t._e()],2)])})),1)],1)],1)],1)},As=[];const Ds={id:"NID0-SID0-EID0",network_id:0,service_id:0,event_id:0,channel_id:"gr000",title:"取得中…",description:"取得中…",detail:{},start_time:"2000-01-01T00:00:00+09:00",end_time:"2000-01-01T00:00:00+09:00",duration:0,is_free:!0,genre:[],video_type:"映像1080i(1125i)、アスペクト比16:9 パンベクトルなし",video_codec:"mpeg2",video_resolution:"1080i",primary_audio_type:"2/0モード(ステレオ)",primary_audio_language:"日本語",primary_audio_sampling_rate:"48kHz",secondary_audio_type:null,secondary_audio_language:null,secondary_audio_sampling_rate:null},Bs={id:"NID0-SID0",network_id:0,service_id:0,transport_stream_id:null,remocon_id:null,channel_id:"gr000",channel_number:"---",channel_name:"取得中…",channel_type:"GR",channel_force:null,channel_comment:null,is_subchannel:!1,is_radiochannel:!1,is_display:!0,viewers:0,program_present:Ds,program_following:Ds};class Ns{static async fetchAll(){const t=await G.get("/channels");return"is_error"in t?(G.showGenericError(t,"チャンネル情報を取得できませんでした。"),null):t.data}static async fetch(t){const e=await G.get(`/channels/${t}`);return"is_error"in e?(G.showGenericError(e,"チャンネル情報を取得できませんでした。"),null):e.data}static async fetchJikkyoSession(t){const e=await G.get(`/channels/${t}/jikkyo`);return"is_error"in e?(G.showGenericError(e,"ニコニコ実況のセッション情報を取得できませんでした。"),null):e.data}}var Ks=Ns;const Ls=(0,a.Q_)("channels",{state:()=>({channel_id:"gr000",channels_list:{GR:[],BS:[],CS:[],CATV:[],SKY:[],STARDIGIO:[]},is_channels_list_initial_updated:!1,last_updated_at:0}),getters:{is_showing_live(){return"gr000"!==this.channel_id},channel(){const t=this.channels_list[D.getChannelType(this.channel_id)];if(void 0===t||0===t.length)return{previous:Bs,current:Bs,next:Bs};const e=t.findIndex((t=>t.channel_id===this.channel_id));if(-1===e){const t=Object.assign(Object.assign({},Bs.program_present),{channel_id:"gr999",title:"チャンネル情報取得エラー",description:"このチャンネル ID のチャンネル情報は存在しません。"}),e=Object.assign(Object.assign({},Bs),{channel_id:"gr999",channel_name:"ERROR",program_present:t,program_following:t});return{previous:e,current:e,next:e}}const s=(()=>{let s=e-1;while(t.length){if(s<=-1&&(s=t.length-1),t[s].is_display)return s;s--}return 0})(),i=(()=>{let s=e+1;while(t.length){if(s>=t.length&&(s=0),t[s].is_display)return s;s++}return 0})();return{previous:t[s],current:t[e],next:t[i]}},channels_list_with_pinned(){var t,e,s,i,a,n,r;const o=st(),l=new Map;if(l.set("ピン留め",[]),l.set("地デジ",[]),!1===this.is_channels_list_initial_updated)return l;l.set("BS",[]),l.set("CS",[]),l.set("CATV",[]),l.set("SKY",[]),l.set("StarDigio",[]);for(const[c,_]of Object.entries(this.channels_list))for(const d of _)if(!1!==d.is_display)switch(o.settings.pinned_channel_ids.includes(d.channel_id)&&(null===(t=l.get("ピン留め"))||void 0===t||t.push(d)),d.channel_type){case"GR":null===(e=l.get("地デジ"))||void 0===e||e.push(d);break;case"BS":null===(s=l.get("BS"))||void 0===s||s.push(d);break;case"CS":null===(i=l.get("CS"))||void 0===i||i.push(d);break;case"CATV":null===(a=l.get("CATV"))||void 0===a||a.push(d);break;case"SKY":null===(n=l.get("SKY"))||void 0===n||n.push(d);break;case"STARDIGIO":null===(r=l.get("StarDigio"))||void 0===r||r.push(d);break}for(const c of[...l.get("ピン留め")]){const t=o.settings.pinned_channel_ids.indexOf(c.channel_id);l.get("ピン留め")[t]=c}for(const[c,_]of l)"ピン留め"!==c&&0===_.length&&l.delete(c);return 1===l.size&&l.has("ピン留め")&&l.delete("ピン留め"),l},channels_list_with_pinned_for_watch(){var t;const e=new Map([...this.channels_list_with_pinned]);return 0===(null===(t=e.get("ピン留め"))||void 0===t?void 0:t.length)&&e.delete("ピン留め"),e}},actions:{getChannel(t){var e;const s=this.channels_list[D.getChannelType(t)];return void 0===s?null:null!==(e=s.find((e=>e.channel_id===t)))&&void 0!==e?e:null},getChannelByRemoconID(t,e){const s=this.channels_list[t],i=s.find((t=>t.remocon_id===e));return null!==i&&void 0!==i?i:null},updateChannel(t,e){const s=D.getChannelType(t);if(void 0===this.channels_list[s])return null;const i=this.channels_list[s].findIndex((e=>e.channel_id===t));-1!==i&&o["default"].set(this.channels_list[s],i,e)},async update(t=!1){const e=async()=>{const t=await Ks.fetchAll();null!==t&&(this.channels_list=t,this.is_channels_list_initial_updated=!0,this.last_updated_at=ht.time())};!0!==this.is_channels_list_initial_updated||!1!==t?await e():ht.time()-this.last_updated_at>60&&e()}}});var Es=Ls,Hs=o["default"].extend({name:"TV-Home",components:{Header:It,Navigation:Rt},data(){return{Utils:ht,ChannelUtils:D,ProgramUtils:mt,tab:null,is_loading:!0,interval_ids:[]}},computed:Object.assign({},(0,a.Kc)(Es,st)),async created(){0===this.settingsStore.settings.pinned_channel_ids.length&&(this.tab=1);const t=60-(new Date).getSeconds();this.interval_ids.push(window.setTimeout((()=>{this.channelsStore.update(!0),this.interval_ids.push(window.setInterval((()=>this.channelsStore.update(!0)),3e4))}),1e3*t)),await this.channelsStore.update(),await ht.sleep(.01),0===this.channelsStore.channels_list_with_pinned.get("ピン留め").length&&(this.tab=1),this.is_loading=!1},beforeDestroy(){for(const t of this.interval_ids)window.clearInterval(t)},methods:{addPinnedChannel(t){this.settingsStore.settings.pinned_channel_ids.push(t);const e=this.channelsStore.getChannel(t);this.$message.show(`${e.channel_name}をピン留めしました。`)},removePinnedChannel(t){this.settingsStore.settings.pinned_channel_ids.splice(this.settingsStore.settings.pinned_channel_ids.indexOf(t),1),0===this.channelsStore.channels_list_with_pinned.get("ピン留め").length&&(this.tab=1);const e=this.channelsStore.getChannel(t);this.$message.show(`${e.channel_name}のピン留めを外しました。`)},isPinnedChannel(t){return this.settingsStore.settings.pinned_channel_ids.includes(t)}}}),Ms=Hs,Us=(0,h.Z)(Ms,$s,As,!1,null,"5395b00e",null),Vs=Us.exports,Rs=s(4437),Fs=s(5294),Gs=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"route-container"},[e("main",{staticClass:"watch-container",class:{"watch-container--control-display":t.is_control_display,"watch-container--panel-display":!!t.Utils.isSmartphoneVertical()||t.is_panel_display,"watch-container--fullscreen":t.is_fullscreen}},[e("nav",{staticClass:"watch-navigation",on:{mousemove:function(e){return t.controlDisplayTimer(e)},touchmove:function(e){return t.controlDisplayTimer(e)},click:function(e){return t.controlDisplayTimer(e)}}},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"watch-navigation__icon",attrs:{to:"/tv/"}},[e("img",{staticClass:"watch-navigation__icon-image",attrs:{src:"/assets/images/icon.svg",width:"23px"}})]),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.right",value:"テレビをみる",expression:"'テレビをみる'",modifiers:{right:!0}}],staticClass:"watch-navigation__link",attrs:{"active-class":"watch-navigation__link--active",to:"/tv/"}},[e("Icon",{staticClass:"watch-navigation__link-icon",attrs:{icon:"fluent:tv-20-regular",width:"26px"}})],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.right",value:"ビデオをみる",expression:"'ビデオをみる'",modifiers:{right:!0}}],staticClass:"watch-navigation__link",attrs:{"active-class":"watch-navigation__link--active",to:"/videos/"}},[e("Icon",{staticClass:"watch-navigation__link-icon",attrs:{icon:"fluent:movies-and-tv-20-regular",width:"26px"}})],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.right",value:"番組表",expression:"'番組表'",modifiers:{right:!0}}],staticClass:"watch-navigation__link",attrs:{"active-class":"watch-navigation__link--active",to:"/timetable/"}},[e("Icon",{staticClass:"watch-navigation__link-icon",attrs:{icon:"fluent:calendar-ltr-20-regular",width:"26px"}})],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.right",value:"録画予約",expression:"'録画予約'",modifiers:{right:!0}}],staticClass:"watch-navigation__link",attrs:{"active-class":"watch-navigation__link--active",to:"/reserves/"}},[e("Icon",{staticClass:"watch-navigation__link-icon",staticStyle:{padding:"0.5px"},attrs:{icon:"fluent:timer-16-regular",width:"26px"}})],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.right",value:"マイリスト",expression:"'マイリスト'",modifiers:{right:!0}}],staticClass:"watch-navigation__link",attrs:{"active-class":"watch-navigation__link--active",to:"/mylist/"}},[e("Icon",{staticClass:"watch-navigation__link-icon",attrs:{icon:"ic:round-playlist-play",width:"26px"}})],1),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.right",value:"キャプチャ",expression:"'キャプチャ'",modifiers:{right:!0}}],staticClass:"watch-navigation__link",attrs:{"active-class":"watch-navigation__link--active",to:"/captures/"}},[e("Icon",{staticClass:"watch-navigation__link-icon",attrs:{icon:"fluent:image-multiple-24-regular",width:"26px"}})],1),e(kt.Z),e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.right",value:"設定",expression:"'設定'",modifiers:{right:!0}}],staticClass:"watch-navigation__link",attrs:{"active-class":"watch-navigation__link--active",to:"/settings/"}},[e("Icon",{staticClass:"watch-navigation__link-icon",attrs:{icon:"fluent:settings-20-regular",width:"26px"}})],1)],1),e("div",{staticClass:"watch-content",on:{mousemove:function(e){return t.controlDisplayTimer(e,!0)},touchmove:function(e){return t.controlDisplayTimer(e,!0)},click:function(e){return t.controlDisplayTimer(e,!0)}}},[e("header",{staticClass:"watch-header"},[e("router-link",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"watch-header__back-icon",attrs:{to:"/tv/"}},[e("Icon",{attrs:{icon:"fluent:arrow-left-12-filled",width:"25px"}})],1),e("img",{staticClass:"watch-header__broadcaster",attrs:{src:`${t.Utils.api_base_url}/channels/${t.channelsStore.channel_id}/logo`}}),e("span",{staticClass:"watch-header__program-title",domProps:{innerHTML:t._s(t.ProgramUtils.decorateProgramInfo(t.channelsStore.channel.current.program_present,"title"))}}),e("span",{staticClass:"watch-header__program-time"},[t._v(" "+t._s(t.ProgramUtils.getProgramTime(t.channelsStore.channel.current.program_present,!0))+" ")]),e(kt.Z),e("span",{staticClass:"watch-header__now"},[t._v(t._s(t.time))])],1),e("div",{staticClass:"watch-player",class:{"watch-player--loading":t.is_loading,"watch-player--virtual-keyboard-display":t.is_virtual_keyboard_display&&t.Utils.hasActiveElementClass("dplayer-comment-input")}},[e("div",{staticClass:"watch-player__background-wrapper"},[e("div",{staticClass:"watch-player__background",class:{"watch-player__background--display":t.is_background_display},style:{backgroundImage:`url(${t.background_url})`}},[e("img",{staticClass:"watch-player__background-logo",attrs:{src:"/assets/images/logo.svg"}})])]),e(ks.Z,{staticClass:"watch-player__buffering",class:{"watch-player__buffering--display":t.is_video_buffering&&(t.is_loading||null!==t.player&&!t.player.video.paused)},attrs:{indeterminate:"",size:"60",width:"6"}}),e("div",{staticClass:"watch-player__dplayer"}),e("div",{staticClass:"watch-player__button",on:{mousemove:function(e){return t.controlDisplayTimer(e)},touchmove:function(e){return t.controlDisplayTimer(e)},click:function(e){return t.controlDisplayTimer(e)}}},[e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.top",value:"前のチャンネル",expression:"'前のチャンネル'",modifiers:{top:!0}}],staticClass:"switch-button switch-button-up",on:{click:function(e){t.is_zapping=!0,t.$router.push({path:`/tv/watch/${t.channelsStore.channel.previous.channel_id}`})}}},[e("Icon",{staticClass:"switch-button-icon",attrs:{icon:"fluent:ios-arrow-left-24-filled",width:"32px",rotate:"1"}})],1),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"switch-button switch-button-panel switch-button-panel--open",on:{click:function(e){t.is_panel_display=!t.is_panel_display}}},[e("Icon",{staticClass:"switch-button-icon",attrs:{icon:"fluent:navigation-16-filled",width:"32px"}})],1),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"},{name:"tooltip",rawName:"v-tooltip.bottom",value:"次のチャンネル",expression:"'次のチャンネル'",modifiers:{bottom:!0}}],staticClass:"switch-button switch-button-down",on:{click:function(e){t.is_zapping=!0,t.$router.push({path:`/tv/watch/${t.channelsStore.channel.next.channel_id}`})}}},[e("Icon",{staticClass:"switch-button-icon",attrs:{icon:"fluent:ios-arrow-right-24-filled",width:"33px",rotate:"1"}})],1)])],1)]),e("div",{staticClass:"watch-panel",on:{mousemove:function(e){return t.controlDisplayTimer(e)}}},[e("div",{staticClass:"watch-panel__header"},[e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"panel-close-button",on:{click:function(e){t.is_panel_display=!1}}},[e("Icon",{staticClass:"panel-close-button__icon",attrs:{icon:"akar-icons:chevron-right",width:"25px"}}),e("span",{staticClass:"panel-close-button__text"},[t._v("閉じる")])],1),e(kt.Z),e("div",{staticClass:"panel-broadcaster"},[e("img",{staticClass:"panel-broadcaster__icon",attrs:{src:`${t.Utils.api_base_url}/channels/${t.channelsStore.channel_id}/logo`}}),e("div",{staticClass:"panel-broadcaster__number"},[t._v(t._s(t.channelsStore.channel.current.channel_number))]),e("div",{staticClass:"panel-broadcaster__name"},[t._v(t._s(t.channelsStore.channel.current.channel_name))])])],1),e("div",{staticClass:"watch-panel__content-container"},[e("Program",{staticClass:"watch-panel__content",class:{"watch-panel__content--active":"Program"===t.tv_panel_active_tab}}),e("Channel",{staticClass:"watch-panel__content",class:{"watch-panel__content--active":"Channel"===t.tv_panel_active_tab}}),e("Comment",{ref:"Comment",staticClass:"watch-panel__content",class:{"watch-panel__content--active":"Comment"===t.tv_panel_active_tab},attrs:{channel:t.channelsStore.channel.current,player:t.player}}),e("Twitter",{ref:"Twitter",staticClass:"watch-panel__content",class:{"watch-panel__content--active":"Twitter"===t.tv_panel_active_tab},attrs:{player:t.player,is_virtual_keyboard_display:t.is_virtual_keyboard_display},on:{panel_folding_requested:function(e){t.is_panel_display=!1}}})],1),e("div",{staticClass:"watch-panel__navigation"},[e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"panel-navigation-button",class:{"panel-navigation-button--active":"Program"===t.tv_panel_active_tab},on:{click:function(e){t.tv_panel_active_tab="Program"}}},[e("Icon",{staticClass:"panel-navigation-button__icon",attrs:{icon:"fa-solid:info-circle",width:"33px"}}),e("span",{staticClass:"panel-navigation-button__text"},[t._v("番組情報")])],1),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"panel-navigation-button",class:{"panel-navigation-button--active":"Channel"===t.tv_panel_active_tab},on:{click:function(e){t.tv_panel_active_tab="Channel"}}},[e("Icon",{staticClass:"panel-navigation-button__icon",attrs:{icon:"fa-solid:broadcast-tower",width:"34px"}}),e("span",{staticClass:"panel-navigation-button__text"},[t._v("チャンネル")])],1),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"panel-navigation-button",class:{"panel-navigation-button--active":"Comment"===t.tv_panel_active_tab},on:{click:function(e){t.tv_panel_active_tab="Comment"}}},[e("Icon",{staticClass:"panel-navigation-button__icon",attrs:{icon:"bi:chat-left-text-fill",width:"29px"}}),e("span",{staticClass:"panel-navigation-button__text"},[t._v("コメント")])],1),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"panel-navigation-button",class:{"panel-navigation-button--active":"Twitter"===t.tv_panel_active_tab},on:{click:function(e){t.tv_panel_active_tab="Twitter"}}},[e("Icon",{staticClass:"panel-navigation-button__icon",attrs:{icon:"fa-brands:twitter",width:"34px"}}),e("span",{staticClass:"panel-navigation-button__text"},[t._v("Twitter")])],1)])])]),e(le.Z,{attrs:{"max-width":"1050",transition:"slide-y-transition"},model:{value:t.shortcut_key_modal,callback:function(e){t.shortcut_key_modal=e},expression:"shortcut_key_modal"}},[e(pt.Z,[e(gt.EB,{staticClass:"px-5 pt-4 pb-3 d-flex align-center font-weight-bold"},[e("Icon",{attrs:{icon:"fluent:keyboard-20-filled",height:"28px"}}),e("span",{staticClass:"ml-3"},[t._v("キーボードショートカット")]),e(kt.Z),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"d-flex align-center rounded-circle cursor-pointer px-2 py-2",on:{click:function(e){t.shortcut_key_modal=!1}}},[e("Icon",{attrs:{icon:"fluent:dismiss-12-filled",width:"23px",height:"23px"}})],1)],1),e("div",{staticClass:"px-5 pb-4"},[e(Fs.Z,t._l(t.shortcut_key_list,(function(s,i){return e(Rs.Z,{key:i,attrs:{cols:"6"}},t._l(s,(function(s){return e("div",{key:s.name,staticClass:"mt-3"},[e("div",{staticClass:"text-subtitle-1 d-flex align-center font-weight-bold"},[e("Icon",{attrs:{icon:s.icon,height:s.icon_height}}),e("span",{staticClass:"ml-2"},[t._v(t._s(s.name))])],1),t._l(s.shortcuts,(function(s){return e("div",{key:s.name,staticClass:"mt-3"},[e("div",{staticClass:"text-subtitle-2 mt-2 d-flex align-center font-weight-medium"},[e("span",{staticClass:"mr-2",domProps:{innerHTML:t._s(s.name)}}),e("div",{staticClass:"ml-auto d-flex align-center flex-shrink-0"},t._l(s.keys,(function(i,a){return e("div",{key:i.name,staticClass:"ml-auto d-flex align-center"},[e("span",{staticClass:"shortcut-key"},[t._l(i.name.split(";"),(function(t){return e("Icon",{directives:[{name:"show",rawName:"v-show",value:!0===i.icon,expression:"key.icon === true"}],key:t,attrs:{icon:t,height:"18px"}})})),!1===i.icon?e("span",{domProps:{innerHTML:t._s(i.name)}}):t._e()],2),a{var t;null===(t=this.watch_session)||void 0===t||t.send(JSON.stringify({type:"startWatching",data:{reconnect:!1}}))}),{signal:this.abort_controller.signal}),this.watch_session.addEventListener("close",(async e=>{!0!==t&&(console.error(`[LiveCommentManager][WatchSession] Connection closed. (Code: ${e.code})`),this.player.notice(`ニコニコ実況との接続が切断されました。(Code: ${e.code})`),await ht.sleep(10),await this.reconnect())}),{signal:this.abort_controller.signal}),this.watch_session.addEventListener("message",(async e=>{const s=JSON.parse(e.data);switch(s.type){case"seat":if(null!==this.keep_seat_interval_id)break;this.keep_seat_interval_id=window.setInterval((()=>{var t;this.watch_session&&this.watch_session.readyState===WebSocket.OPEN?this.watch_session.send(JSON.stringify({type:"keepSeat"})):window.clearInterval(null!==(t=this.keep_seat_interval_id)&&void 0!==t?t:0)}),1e3*s.data.keepIntervalSec);break;case"ping":this.watch_session.send(JSON.stringify({type:"pong"}));break;case"error":{if("COMMENT_POST_NOT_ALLOWED"===s.data.code||"INVALID_MESSAGE"===s.data.code)break;let t=`ニコニコ実況でエラーが発生しています。(Code: ${s.data.code})`;switch(s.data.code){case"CONNECT_ERROR":t="ニコニコ実況のコメントサーバーに接続できません。";break;case"CONTENT_NOT_READY":t="ニコニコ実況が配信できない状態です。";break;case"NO_THREAD_AVAILABLE":t="ニコニコ実況のコメントスレッドを取得できません。";break;case"NO_ROOM_AVAILABLE":t="ニコニコ実況のコメント部屋を取得できません。";break;case"NO_PERMISSION":t="ニコニコ実況の API にアクセスする権限がありません。";break;case"NOT_ON_AIR":t="ニコニコ実況が放送中ではありません。";break;case"BROADCAST_NOT_FOUND":t="ニコニコ実況の配信情報を取得できません。";break;case"INTERNAL_SERVERERROR":t="ニコニコ実況でサーバーエラーが発生しています。";break}console.error(`[LiveCommentManager][WatchSession] Error occurred. (Code: ${s.data.code})`),this.player.notice(t),await ht.sleep(5),await this.reconnect();break}case"reconnect":await this.reconnect();break;case"disconnect":{t=!0;let e=`ニコニコ実況との接続が切断されました。(${s.data.reason})`;switch(s.data.reason){case"TAKEOVER":e="ニコニコ実況の番組から追い出されました。";break;case"NO_PERMISSION":e="ニコニコ実況の番組の座席を取得できませんでした。";break;case"END_PROGRAM":e="ニコニコ実況がリセットされたか、コミュニティの番組が終了しました。";break;case"PING_TIMEOUT":e="コメントサーバーとの接続生存確認に失敗しました。";break;case"TOO_MANY_CONNECTIONS":e="ニコニコ実況の同一ユーザからの接続数上限を越えています。";break;case"TOO_MANY_WATCHINGS":e="ニコニコ実況の同一ユーザからの視聴番組数上限を越えています。";break;case"CROWDED":e="ニコニコ実況の番組が満席です。";break;case"MAINTENANCE_IN":e="ニコニコ実況はメンテナンス中です。";break;case"SERVICE_TEMPORARILY_UNAVAILABLE":e="ニコニコ実況で一時的にサーバーエラーが発生しています。";break}console.error(`[LiveCommentManager][WatchSession] Disconnected. (Reason: ${s.data.reason})`),this.player.notice(e),await ht.sleep(5),await this.reconnect();break}}}),{signal:this.abort_controller.signal}),new Promise((t=>{this.watch_session.addEventListener("message",(async e=>{const s=JSON.parse(e.data);if("room"===s.type)return this.vpos_base_timestamp=rt()(s.data.vposBaseTime).valueOf(),console.log(`[LiveCommentManager][WatchSession] Connected.\nThread ID: ${s.data.threadId}\n`),t({is_success:!0,detail:"視聴セッションを取得しました。",message_server_url:s.data.messageServer.uri,thread_id:s.data.threadId,your_post_key:s.data.yourPostKey?s.data.yourPostKey:null})}),{signal:this.abort_controller.signal})})))}initCommentSession(t){const e=[];let s=!1;this.comment_session=new WebSocket(t.message_server_url),this.comment_session.addEventListener("open",(()=>{this.comment_session.send(JSON.stringify([{ping:{content:"rs:0"}},{ping:{content:"ps:0"}},{thread:{version:"20061206",thread:t.thread_id,threadkey:t.your_post_key,user_id:"",res_from:-50}},{ping:{content:"pf:0"}},{ping:{content:"rf:0"}}]))}),{signal:this.abort_controller.signal}),this.comment_session.addEventListener("message",(async t=>{const i=JSON.parse(t.data);if(void 0!==i.thread&&0!==i.thread.resultcode)return void console.error(`[LiveCommentManager][CommentSession] Connection failed. (Code: ${i.thread.resultcode})`);if(void 0!==i.ping&&"rf:0"===i.ping.content)return s=!0,void this.on_initial_comments_received(e);const a=i.chat;if(void 0===a||void 0===a.content||""===a.content||a.yourpost&&1===a.yourpost)return;const{color:n,position:r,size:o}=it.parseCommentCommand(a.mail);if(it.isMutedComment(a.content,a.user_id,n,r,o))return;const l={id:a.no,text:a.content,time:rt()(1e3*a.date).format("HH:mm:ss"),user_id:a.user_id,my_post:!1};if(!1===s)return void e.push(l);let c=0;this.player.video.buffered.length>=1&&(c=this.player.video.buffered.end(0));const _=c-this.player.video.currentTime;await ht.sleep(_),this.on_comment_received(l),!1===this.player.video.paused&&this.player.danmaku.draw({text:a.content,color:n,type:r,size:o})}),{signal:this.abort_controller.signal})}sendComment(t){const e={"#FFEAEA":"white","#F02840":"red","#FD7E80":"pink","#FDA708":"orange","#FFE133":"yellow","#64DD17":"green","#00D4F5":"cyan","#4763FF":"blue"},s={top:"ue",right:"naka",bottom:"shita"},i=Math.round((rt()().valueOf()-this.vpos_base_timestamp)/10);if(null===this.watch_session||this.watch_session.readyState!==WebSocket.OPEN)return console.error("[LiveCommentManager][WatchSession] Comment sending failed. (Connection is not established.)"),void t.error("コメントの送信に失敗しました。WebSocket 接続が確立されていません。");this.watch_session.send(JSON.stringify({type:"postComment",data:{text:t.data.text,color:e[t.data.color.toUpperCase()],position:s[t.data.type],size:t.data.size,vpos:i,isAnonymous:!0}}));const a=new AbortController;this.watch_session.addEventListener("message",(e=>{const s=JSON.parse(e.data);switch(s.type){case"postCommentResult":t.success(),a.abort();break;case"error":{let e=`コメントの送信に失敗しました。(${s.data.code})`;switch(s.data.code){case"COMMENT_POST_NOT_ALLOWED":e="コメントが許可されていません。";break;case"INVALID_MESSAGE":e="コメント内容が無効です。";break}console.error(`[LiveCommentManager][WatchSession] Comment sending failed. (Code: ${s.data.code})`),t.error(e),a.abort();break}}}),{signal:a.signal})}async reconnect(){console.warn("[LiveCommentManager][WatchSession] Reconnecting..."),this.player.notice("ニコニコ実況に再接続しています…"),this.destroy();const t=await this.initSession();!1===t.is_success&&(console.error("[LiveCommentManager][WatchSession] Reconnection failed."),this.player.notice(t.detail))}destroy(){this.abort_controller.abort(),this.abort_controller=new AbortController,null!==this.watch_session&&(this.watch_session.close(),this.watch_session=null),null!==this.comment_session&&(this.comment_session.close(),this.comment_session=null),window.clearInterval(this.keep_seat_interval_id),this.keep_seat_interval_id=null,this.vpos_base_timestamp=0,console.log("[LiveCommentManager][WatchSession] Destroyed.")}}var li=oi,ci=o["default"].extend({name:"Panel-CommentTab",components:{CommentMuteSettings:cs},data(){return{Utils:ht,is_manual_scroll:!1,is_auto_scrolling:!1,comment_list:[],comment_list_element:null,is_comment_list_dropdown_display:!1,comment_list_dropdown_top:0,comment_list_dropdown_comment:null,live_comment_manager:null,initialize_failed_message:null,visibilitychange_listener:null,resize_observer:null,resize_observer_element:null,comment_mute_settings_modal:!1}},computed:Object.assign({},(0,a.Kc)(R)),created(){this.userStore.fetchUser()},mounted(){null===this.comment_list_element&&(this.comment_list_element=this.$el.querySelector(".comment-list"));let t=!1;this.comment_list_element.onmousedown=e=>{const s=e.clientX-this.comment_list_element.getBoundingClientRect().left;s>this.comment_list_element.clientWidth&&(t=!0)},this.comment_list_element.onmouseup=e=>{const s=e.clientX-this.comment_list_element.getBoundingClientRect().left;s>this.comment_list_element.clientWidth&&(t=!1)};const e=()=>{t=!0,window.setTimeout((()=>t=!1),100)};let s=!1;this.comment_list_element.ontouchstart=()=>s=!0,this.comment_list_element.ontouchend=()=>s=!1,this.comment_list_element.ontouchmove=()=>!0===s?e():"",this.comment_list_element.onwheel=e,this.comment_list_element.onscroll=async()=>{!1===this.is_auto_scrolling&&!0===t&&(this.is_manual_scroll=!0,await ht.sleep(.1),this.comment_list_element.scrollTop+this.comment_list_element.offsetHeight>this.comment_list_element.scrollHeight-10&&(this.is_manual_scroll=!1))}},beforeDestroy(){this.destroy(),null!==this.resize_observer&&this.resize_observer.unobserve(this.resize_observer_element)},methods:{showCommentListDropdown(t,e){const s=this.$refs.comment_list_wrapper.getBoundingClientRect(),i=76,a=t.currentTarget.getBoundingClientRect();this.comment_list_dropdown_top=a.top-s.top,this.comment_list_dropdown_top+i>s.height&&(this.comment_list_dropdown_top=this.comment_list_dropdown_top-i+a.height),this.comment_list_dropdown_comment=e,this.is_comment_list_dropdown_display=!0},hideCommentListDropdown(){this.is_comment_list_dropdown_display=!1,this.comment_list=this.comment_list.filter((t=>!1===it.isMutedComment(t.text,t.user_id)))},addMutedKeywords(){it.addMutedKeywords(this.comment_list_dropdown_comment.text),this.hideCommentListDropdown()},addMutedNiconicoUserIds(){it.addMutedNiconicoUserIDs(this.comment_list_dropdown_comment.user_id),this.hideCommentListDropdown()},async scrollCommentList(t=!1){if(!0===this.is_comment_list_dropdown_display&&(this.is_manual_scroll=!0),!0!==this.is_manual_scroll){this.is_auto_scrolling=!0;for(let e=0;e<3;e++)await ht.sleep(.01),!0===t?this.comment_list_element.scrollTo({top:this.comment_list_element.scrollHeight,left:0,behavior:"smooth"}):this.comment_list_element.scrollTo(0,this.comment_list_element.scrollHeight);await ht.sleep(.1),this.is_auto_scrolling=!1}},initReserveObserver(){null!==this.resize_observer&&this.resize_observer.unobserve(this.resize_observer_element),this.resize_observer_element=document.querySelector(".watch-player");let t=null;const e=()=>{const e=document.querySelector(".dplayer-video-wrap-aspect"),s=document.querySelector(".dplayer-danmaku");if(null===this.resize_observer_element||null===this.resize_observer_element.clientHeight)return;if(null===e||null===e.clientHeight)return;const i=(this.resize_observer_element.clientHeight-e.clientHeight)/2,a=ht.isSmartphoneVertical()?0:window.matchMedia("(max-height: 450px)").matches?50:66;if(i0===e?t:l(e,t%e),c=l(r,o),_=`${r/c} / ${o/c}`;s.style.transition="none",s.style.setProperty("--comment-area-aspect-ratio",_),s.style.setProperty("--comment-area-vertical-margin",`${n}px`),window.clearTimeout(t),window.setTimeout((()=>{s.style.transition=""}),200)}else s.style.removeProperty("--comment-area-aspect-ratio"),s.style.removeProperty("--comment-area-vertical-margin")};this.resize_observer=new ResizeObserver(e),this.resize_observer.observe(this.resize_observer_element),window.setTimeout(e,600)},async initSession(t,e){this.initReserveObserver();const s=[],i=500;this.live_comment_manager=new li(t,e,(async t=>{this.comment_list.push(...t),this.scrollCommentList()}),(async t=>{"hidden"!==document.visibilityState?(this.comment_list.length>=i&&!1===this.is_manual_scroll&&this.comment_list.splice(0,Math.max(0,this.comment_list.length-i)),this.comment_list.push(t),this.scrollCommentList()):s.push(t)})),this.visibilitychange_listener=()=>{if("visible"===document.visibilityState){const t=this.comment_list.length+s.length;t>=i&&!1===this.is_manual_scroll&&this.comment_list.splice(0,Math.max(0,t-i)),this.comment_list.push(...s),s.length=0,this.scrollCommentList()}},document.addEventListener("visibilitychange",this.visibilitychange_listener);const a=await this.live_comment_manager.initSession();!1===a.is_success&&(this.initialize_failed_message=a.detail)},sendComment(t){null===this.initialize_failed_message?null!==this.userStore.user?null!==this.userStore.user.niconico_user_id?!1!==this.userStore.user.niconico_user_premium||"top"!==t.data.type&&"bottom"!==t.data.type?!1!==this.userStore.user.niconico_user_premium||"big"!==t.data.size?this.live_comment_manager.sendComment(t):t.error("コメントサイズを大きめに設定するには、ニコニコアカウントのプレミアム会員登録が必要です。"):t.error("コメントを上下に固定するには、ニコニコアカウントのプレミアム会員登録が必要です。"):t.error("コメントするには、ニコニコアカウントと連携してください。"):t.error("コメントするには、KonomiTV アカウントにログインしてください。"):t.error(this.initialize_failed_message)},destroy(){null!==this.visibilitychange_listener&&(document.removeEventListener("visibilitychange",this.visibilitychange_listener),this.visibilitychange_listener=null),null!==this.live_comment_manager&&(this.live_comment_manager.destroy(),this.live_comment_manager=null),this.initialize_failed_message=null,this.comment_list=[]}}}),_i=ci,di=(0,h.Z)(_i,ni,ri,!1,null,"6f8c784f",null),ui=di.exports,mi=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"program-container"},[e("section",{staticClass:"program-broadcaster"},[e("img",{staticClass:"program-broadcaster__icon",attrs:{src:`${t.Utils.api_base_url}/channels/${t.channelsStore.channel_id}/logo`}}),e("div",{staticClass:"program-broadcaster__number"},[t._v("Ch: "+t._s(t.channelsStore.channel.current.channel_number))]),e("div",{staticClass:"program-broadcaster__name"},[t._v(t._s(t.channelsStore.channel.current.channel_name))])]),e("section",{staticClass:"program-info"},[e("h1",{staticClass:"program-info__title",domProps:{innerHTML:t._s(t.ProgramUtils.decorateProgramInfo(t.channelsStore.channel.current.program_present,"title"))}}),e("div",{staticClass:"program-info__time"},[t._v(" "+t._s(t.ProgramUtils.getProgramTime(t.channelsStore.channel.current.program_present))+" ")]),e("div",{staticClass:"program-info__description",domProps:{innerHTML:t._s(t.ProgramUtils.decorateProgramInfo(t.channelsStore.channel.current.program_present,"description"))}}),e("div",{staticClass:"program-info__genre-container"},t._l(t.ProgramUtils.getAttribute(t.channelsStore.channel.current.program_present,"genre",[]),(function(s,i){return e("div",{key:i,staticClass:"program-info__genre"},[t._v(" "+t._s(s.major)+" / "+t._s(s.middle)+" ")])})),0),e("div",{staticClass:"program-info__next"},[e("span",{staticClass:"program-info__next-decorate"},[t._v("NEXT")]),e("Icon",{staticClass:"program-info__next-icon",attrs:{icon:"fluent:fast-forward-20-filled",width:"16px"}})],1),e("span",{staticClass:"program-info__next-title",domProps:{innerHTML:t._s(t.ProgramUtils.decorateProgramInfo(t.channelsStore.channel.current.program_following,"title"))}}),e("div",{staticClass:"program-info__next-time"},[t._v(" "+t._s(t.ProgramUtils.getProgramTime(t.channelsStore.channel.current.program_following))+" ")]),e("div",{staticClass:"program-info__status"},[e("div",{staticClass:"program-info__status-force",class:`program-info__status-force--${t.ChannelUtils.getChannelForceType(t.channelsStore.channel.current.channel_force)}`},[e("Icon",{attrs:{icon:"fa-solid:fire-alt",height:"14px"}}),e("span",{staticClass:"ml-2"},[t._v("勢い:")]),e("span",{staticClass:"ml-2"},[t._v(t._s(t.ProgramUtils.getAttribute(t.channelsStore.channel.current,"channel_force","--"))+" コメ/分")])],1),e("div",{staticClass:"program-info__status-viewers ml-5"},[e("Icon",{attrs:{icon:"fa-solid:eye",height:"14px"}}),e("span",{staticClass:"ml-2"},[t._v("視聴数:")]),e("span",{staticClass:"ml-1"},[t._v(t._s(t.channelsStore.channel.current.viewers))])],1)])]),e("section",{staticClass:"program-detail-container"},t._l(t.ProgramUtils.getAttribute(t.channelsStore.channel.current.program_present,"detail",{}),(function(s,i){return e("div",{key:i,staticClass:"program-detail"},[e("h2",{staticClass:"program-detail__heading"},[t._v(t._s(i))]),e("div",{staticClass:"program-detail__text",domProps:{innerHTML:t._s(t.Utils.URLtoLink(s))}})])})),0)])},hi=[],pi=o["default"].extend({name:"Panel-ProgramTab",data(){return{Utils:ht,ChannelUtils:D,ProgramUtils:mt}},computed:Object.assign({},(0,a.Kc)(Es))}),gi=pi,vi=(0,h.Z)(gi,mi,hi,!1,null,"12609bdf",null),fi=vi.exports,wi=function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"twitter-container"},[e(le.Z,{attrs:{"content-class":"zoom-capture-modal-container","max-width":"980",transition:"slide-y-transition"},model:{value:t.zoom_capture_modal,callback:function(e){t.zoom_capture_modal=e},expression:"zoom_capture_modal"}},[e("div",{staticClass:"zoom-capture-modal"},[e("img",{staticClass:"zoom-capture-modal__image",attrs:{src:t.zoom_capture?t.zoom_capture.image_url:""}}),e("a",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"zoom-capture-modal__download",attrs:{href:t.zoom_capture?t.zoom_capture.image_url:"",download:t.zoom_capture?t.zoom_capture.filename:""}},[e("Icon",{attrs:{icon:"fa6-solid:download",width:"45px"}})],1)])]),e("div",{staticClass:"tab-container"},[e("div",{staticClass:"tab-content tab-content--search",class:{"tab-content--active":"Search"===t.twitter_active_tab}},[e("div",{staticClass:"search px-4"},[t._v(" リアルタイム検索機能は鋭意開発中です。 ")])]),e("div",{staticClass:"tab-content tab-content--timeline",class:{"tab-content--active":"Timeline"===t.twitter_active_tab}},[e("div",{staticClass:"search px-4"},[t._v(" タイムライン機能は鋭意開発中です。 ")])]),e("div",{staticClass:"tab-content tab-content--capture",class:{"tab-content--active":"Capture"===t.twitter_active_tab}},[e("div",{staticClass:"captures"},t._l(t.captures,(function(s){return e("div",{key:s.image_url,staticClass:"capture",class:{"capture--selected":s.selected,"capture--focused":s.focused,"capture--disabled":!s.selected&&t.tweet_captures.length>=4},on:{click:function(e){return t.clickCapture(s)}}},[e("img",{staticClass:"capture__image",attrs:{src:s.image_url}}),e("div",{staticClass:"capture__disabled-cover"}),e("div",{staticClass:"capture__selected-number"},[t._v(t._s(t.tweet_captures.findIndex((t=>t===s.blob))+1))]),e("Icon",{staticClass:"capture__selected-checkmark",attrs:{icon:"fluent:checkmark-circle-16-filled"}}),e("div",{staticClass:"capture__selected-border"}),e("div",{staticClass:"capture__focused-border"}),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"capture__zoom",on:{click:function(e){e.preventDefault(),e.stopPropagation(),t.zoom_capture_modal=!0,t.zoom_capture=s},mousedown:function(t){t.preventDefault(),t.stopPropagation()}}},[e("Icon",{attrs:{icon:"fluent:zoom-in-16-regular",width:"32px"}})],1)],1)})),0),e("div",{directives:[{name:"show",rawName:"v-show",value:0===t.captures.length,expression:"captures.length === 0"}],staticClass:"capture-announce"},[e("div",{staticClass:"capture-announce__heading"},[t._v("まだキャプチャがありません。")]),t._m(0)])])]),e("div",{staticClass:"tab-button-container"},[e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"tab-button",class:{"tab-button--active":"Search"===t.twitter_active_tab},on:{click:function(e){t.twitter_active_tab="Search"}}},[e("Icon",{attrs:{icon:"fluent:search-16-filled",height:"18px"}}),e("span",{staticClass:"tab-button__text"},[t._v("ツイート検索")])],1),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"tab-button",class:{"tab-button--active":"Timeline"===t.twitter_active_tab},on:{click:function(e){t.twitter_active_tab="Timeline"}}},[e("Icon",{attrs:{icon:"fluent:home-16-regular",height:"18px"}}),e("span",{staticClass:"tab-button__text"},[t._v("タイムライン")])],1),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"tab-button",class:{"tab-button--active":"Capture"===t.twitter_active_tab},on:{click:function(e){t.twitter_active_tab="Capture"}}},[e("Icon",{attrs:{icon:"fluent:image-copy-20-regular",height:"18px"}}),e("span",{staticClass:"tab-button__text"},[t._v("キャプチャ")])],1)]),e("div",{staticClass:"tweet-form",class:{"tweet-form--focused":t.is_tweet_hashtag_form_focused||t.is_tweet_text_form_focused,"tweet-form--virtual-keyboard-display":t.is_virtual_keyboard_display&&(t.Utils.hasActiveElementClass("tweet-form__hashtag-form")||t.Utils.hasActiveElementClass("tweet-form__textarea"))&&(()=>(t.is_hashtag_list_display=!1,!0))()}},[e("div",{staticClass:"tweet-form__hashtag"},[e("input",{directives:[{name:"model",rawName:"v-model",value:t.tweet_hashtag,expression:"tweet_hashtag"}],staticClass:"tweet-form__hashtag-form",attrs:{type:"search",placeholder:"#ハッシュタグ",spellcheck:"false"},domProps:{value:t.tweet_hashtag},on:{input:[function(e){e.target.composing||(t.tweet_hashtag=e.target.value)},function(e){return t.updateTweetLetterCount()}],focus:function(e){t.is_tweet_hashtag_form_focused=!0},blur:function(e){t.is_tweet_hashtag_form_focused=!1},change:function(e){t.tweet_hashtag=t.formatHashtag(t.tweet_hashtag),t.updateTweetLetterCount()}}}),e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"tweet-form__hashtag-list-button",on:{click:function(e){return t.clickHashtagListButton()}}},[e("Icon",{attrs:{icon:"fluent:clipboard-text-ltr-32-regular",height:"22px"}})],1)]),e("textarea",{directives:[{name:"model",rawName:"v-model",value:t.tweet_text,expression:"tweet_text"}],ref:"tweet_text",staticClass:"tweet-form__textarea",attrs:{placeholder:"ツイート",spellcheck:"false"},domProps:{value:t.tweet_text},on:{input:[function(e){e.target.composing||(t.tweet_text=e.target.value)},function(e){return t.updateTweetLetterCount()}],paste:function(e){return t.pasteClipboardData(e)},focus:function(e){t.is_tweet_text_form_focused=!0},blur:function(e){t.is_tweet_text_form_focused=!1}}}),e("div",{staticClass:"tweet-form__control"},[e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"account-button",class:{"account-button--no-login":!t.is_logged_in_twitter},on:{click:function(e){return t.clickAccountButton()}}},[e("img",{staticClass:"account-button__icon",attrs:{src:t.is_logged_in_twitter?t.selected_twitter_account.icon_url:"/assets/images/account-icon-default.png"}}),e("span",{staticClass:"account-button__screen-name"},[t._v(" "+t._s(t.is_logged_in_twitter?`@${t.selected_twitter_account.screen_name}`:"連携されていません")+" ")]),e("Icon",{staticClass:"account-button__menu",attrs:{icon:"fluent:more-circle-20-regular",width:"22px"}})],1),e("div",{staticClass:"limit-meter"},[e("div",{staticClass:"limit-meter__content",class:{"limit-meter__content--yellow":t.tweet_letter_count<=20,"limit-meter__content--red":t.tweet_letter_count<=0}},[e("Icon",{staticStyle:{"margin-right":"-2px"},attrs:{icon:"fa-brands:twitter",width:"12px"}}),e("span",[t._v(t._s(t.tweet_letter_count))])],1),e("div",{staticClass:"limit-meter__content"},[e("Icon",{attrs:{icon:"fluent:image-16-filled",width:"14px"}}),e("span",[t._v(t._s(t.tweet_captures.length)+"/4")])],1)]),e("button",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"tweet-button",attrs:{disabled:!t.is_logged_in_twitter||t.tweet_letter_count<0||140===t.tweet_letter_count&&0===t.tweet_captures.length},on:{click:function(e){return t.sendTweet()},touchstart:function(e){return t.sendTweet()}}},[e("Icon",{attrs:{icon:"fa-brands:twitter",height:"16px"}}),e("span",{staticClass:"ml-1"},[t._v("ツイート")])],1)])]),e("div",{staticClass:"hashtag-list",class:{"hashtag-list--display":t.is_hashtag_list_display,"hashtag-list--virtual-keyboard-display":t.is_virtual_keyboard_display&&t.Utils.hasActiveElementClass("hashtag__input")}},[e("div",{staticClass:"hashtag-heading"},[e("div",{staticClass:"hashtag-heading__text"},[e("Icon",{attrs:{icon:"charm:hash",width:"17px"}}),e("span",{staticClass:"ml-1"},[t._v("ハッシュタグリスト")])],1),e("button",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"hashtag-heading__add-button",on:{click:function(e){t.saved_twitter_hashtags.push({id:t.Utils.time(),text:"#ここにハッシュタグを入力",editing:!1})}}},[e("Icon",{attrs:{icon:"fluent:add-12-filled",width:"17px"}}),e("span",{staticClass:"ml-1"},[t._v("追加")])],1)]),e("draggable",{staticClass:"hashtag-container",attrs:{handle:".hashtag__sort-handle"},model:{value:t.saved_twitter_hashtags,callback:function(e){t.saved_twitter_hashtags=e},expression:"saved_twitter_hashtags"}},t._l(t.saved_twitter_hashtags,(function(s){return e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple",value:!s.editing,expression:"!hashtag.editing"}],key:s.id,staticClass:"hashtag",class:{"hashtag--editing":s.editing},on:{click:function(e){return t.clickHashtag(s)}}},[e("input",{directives:[{name:"model",rawName:"v-model",value:s.text,expression:"hashtag.text"}],staticClass:"hashtag__input",attrs:{type:"search",spellcheck:"false",disabled:!s.editing},domProps:{value:s.text},on:{click:function(t){t.stopPropagation()},input:function(e){e.target.composing||t.$set(s,"text",e.target.value)}}}),e("button",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"hashtag__edit-button",on:{click:function(e){e.preventDefault(),e.stopPropagation(),s.editing=!s.editing,s.text=t.formatHashtag(s.text,!0),t.updateTweetLetterCount()}}},[e("Icon",{attrs:{icon:s.editing?"fluent:checkmark-16-filled":"fluent:edit-16-filled",width:"17px"}})],1),e("button",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],staticClass:"hashtag__delete-button",on:{click:function(e){e.preventDefault(),e.stopPropagation(),t.saved_twitter_hashtags.splice(t.saved_twitter_hashtags.indexOf(s),1)}}},[e("Icon",{attrs:{icon:"fluent:delete-16-filled",width:"17px"}})],1),e("div",{staticClass:"hashtag__sort-handle"},[e("Icon",{attrs:{icon:"material-symbols:drag-handle-rounded",width:"17px"}})],1)])})),0)],1),e("div",{staticClass:"twitter-account-list",class:{"twitter-account-list--display":t.is_twitter_account_list_display}},t._l(t.userStore.user?t.userStore.user.twitter_accounts:[],(function(s){return e("div",{directives:[{def:xt.Z,name:"ripple",rawName:"v-ripple"}],key:s.id,staticClass:"twitter-account",on:{click:function(e){return t.updateSelectedTwitterAccount(s)}}},[e("img",{staticClass:"twitter-account__icon",attrs:{src:s.icon_url}}),e("div",{staticClass:"twitter-account__info"},[e("div",{staticClass:"twitter-account__name"},[t._v(t._s(s.name))]),e("div",{staticClass:"twitter-account__screen-name"},[t._v("@"+t._s(s.screen_name))])]),e("Icon",{directives:[{name:"show",rawName:"v-show",value:s.id===t.settingsStore.settings.selected_twitter_account_id,expression:"twitter_account.id === settingsStore.settings.selected_twitter_account_id"}],staticClass:"twitter-account__check",attrs:{icon:"fluent:checkmark-16-filled",width:"24px"}})],1)})),0)],1)},yi=[function(){var t=this,e=t._self._c;t._self._setupProxy;return e("div",{staticClass:"capture-announce__text"},[e("p",{staticClass:"mt-0 mb-0"},[t._v("プレイヤーのキャプチャボタンやショートカットキーでキャプチャを撮ると、ここに表示されます。")]),e("p",{staticClass:"mt-2 mb-0"},[t._v("表示されたキャプチャを選択してからツイートすると、キャプチャを付けてツイートできます。")])])}],bi=s(9980),Ci=s.n(bi),ki=o["default"].extend({name:"Panel-TwitterTab",components:{draggable:Ci()},props:{player:{type:null,required:!0},is_virtual_keyboard_display:{type:Boolean,required:!0}},data(){return{Utils:ht,is_logged_in_twitter:!1,selected_twitter_account:null,is_twitter_account_list_display:!1,saved_twitter_hashtags:st().settings.saved_twitter_hashtags.map(((t,e)=>({id:ht.time()+e,text:t,editing:!1}))),is_hashtag_list_display:!1,twitter_active_tab:st().settings.twitter_active_tab,zoom_capture_modal:!1,zoom_capture:null,captures:[],captures_element:null,is_tweet_hashtag_form_focused:!1,is_tweet_text_form_focused:!1,tweet_hashtag:"",tweet_text:"",tweet_captures:[],tweet_letter_count:140,is_tweet_sending:!1}},computed:Object.assign({},(0,a.Kc)(Es,st,R)),async created(){if(await this.userStore.fetchUser(),!0===this.userStore.is_logged_in&&this.userStore.user.twitter_accounts.length>0){this.is_logged_in_twitter=!0,null!==this.settingsStore.settings.selected_twitter_account_id&&this.userStore.user.twitter_accounts.some((t=>t.id===this.settingsStore.settings.selected_twitter_account_id))||(this.settingsStore.settings.selected_twitter_account_id=this.userStore.user.twitter_accounts[0].id);const t=this.userStore.user.twitter_accounts.findIndex((t=>t.id===this.settingsStore.settings.selected_twitter_account_id));this.selected_twitter_account=this.userStore.user.twitter_accounts[t]}this.tweet_hashtag=this.formatHashtag(this.tweet_hashtag),this.updateTweetLetterCount()},beforeDestroy(){for(const t of this.captures)URL.revokeObjectURL(t.image_url)},watch:{saved_twitter_hashtags:{deep:!0,handler(){this.settingsStore.settings.saved_twitter_hashtags=this.saved_twitter_hashtags.map((t=>t.text))}}},methods:{updateTweetLetterCount(){this.tweet_letter_count=140-[...this.tweet_hashtag].length-[...this.tweet_text].length},pasteClipboardData(t){for(const e of t.clipboardData.items)if(e.type.startsWith("image/")){const t=e.getAsFile();this.addCaptureList(t,t.name)}},clickHashtagListButton(){this.is_hashtag_list_display=!this.is_hashtag_list_display;for(const t of this.saved_twitter_hashtags)t.editing=!1},clickHashtag(t){this.tweet_hashtag=t.text,this.tweet_hashtag=this.formatHashtag(this.tweet_hashtag),this.updateTweetLetterCount(),window.setTimeout((()=>this.is_hashtag_list_display=!1),150)},clickAccountButton(){if(!this.is_logged_in_twitter)return document.fullscreenElement&&document.exitFullscreen(),void this.$router.push({path:"/settings/twitter"});this.is_twitter_account_list_display=!this.is_twitter_account_list_display,!0===this.is_twitter_account_list_display&&(this.is_hashtag_list_display=!1)},updateSelectedTwitterAccount(t){this.settingsStore.settings.selected_twitter_account_id=t.id,this.selected_twitter_account=t,window.setTimeout((()=>this.is_twitter_account_list_display=!1),150)},clickCapture(t){if(this.tweet_captures.length<4&&!1===t.selected)t.selected=!0,this.tweet_captures.push(t.blob);else{const e=this.tweet_captures.findIndex((e=>e===t.blob));e>-1&&this.tweet_captures.splice(e,1),t.selected=!1}},async addCaptureList(t,e){null===this.captures_element&&(this.captures_element=this.$el.querySelector(".tab-content--capture")),this.captures.length>50&&(URL.revokeObjectURL(this.captures[0].image_url),this.captures.shift());const s=URL.createObjectURL(t);this.captures.push({blob:t,filename:e,image_url:s,selected:!1,focused:!1}),this.$nextTick((()=>{this.captures_element.scrollTo({top:this.captures_element.scrollHeight,behavior:"smooth"})}))},async drawProgramTitleOnCapture(t){var e,s;const i=await createImageBitmap(t),a="OffscreenCanvas"in window?new OffscreenCanvas(i.width,i.height):document.createElement("canvas"),n=a.getContext("2d",{alpha:!1,desynchronized:!0,willReadFrequently:!1});n.drawImage(i,0,0),i.close(),n.font='bold 22px "YakuHanJPs", "Open Sans", "Hiragino Sans", "Noto Sans JP", sans-serif',n.fillStyle="rgba(255, 255, 255, 70%)",n.shadowColor="rgba(0, 0, 0, 100%)",n.shadowBlur=4,n.shadowOffsetX=0,n.shadowOffsetY=0;const r=null!==(s=null===(e=this.channelsStore.channel.current.program_present)||void 0===e?void 0:e.title)&&void 0!==s?s:"放送休止";switch(this.settingsStore.settings.tweet_capture_watermark_position){case"TopLeft":n.textAlign="left",n.textBaseline="top",n.fillText(r,16,12);break;case"TopRight":n.textAlign="right",n.textBaseline="top",n.fillText(r,a.width-16,12);break;case"BottomLeft":n.textAlign="left",n.textBaseline="bottom",n.fillText(r,16,a.height-12);break;case"BottomRight":n.textAlign="right",n.textBaseline="bottom",n.fillText(r,a.width-16,a.height-12);break}return a instanceof OffscreenCanvas?await a.convertToBlob({type:"image/jpeg",quality:1}):new Promise((t=>a.toBlob((e=>t(e)),"image/jpeg",1)))},getChannelHashtag(t){return t.startsWith("NHK総合")?"#nhk":t.startsWith("NHKEテレ")?"#etv":t.startsWith("日テレ")?"#ntv":t.startsWith("読売テレビ")?"#ytv":t.startsWith("中京テレビ")?"#chukyotv":t.startsWith("テレビ朝日")?"#tvasahi":t.startsWith("ABCテレビ")?"#abc":t.startsWith("メ~テレ")?"#nagoyatv":t.startsWith("TBS")&&!t.includes("TBSチャンネル")?"#tbs":t.startsWith("MBS")?"#mbs":t.startsWith("CBC")?"#cbc":t.startsWith("テレビ東京")?"#tvtokyo":t.startsWith("テレビ大阪")?"#tvo":t.startsWith("テレビ愛知")?"#tva":t.startsWith("フジテレビ")?"#fujitv":t.startsWith("関西テレビ")?"#kantele":t.startsWith("東海テレビ")?"#tokaitv":t.startsWith("TOKYO MX")?"#tokyomx":t.startsWith("tvk")?"#tvk":t.startsWith("チバテレ")?"#chibatv":t.startsWith("テレ玉")?"#teletama":t.startsWith("サンテレビ")?"#suntv":t.startsWith("KBS京都")?"#kbs":t.startsWith("NHKBS1")?"#nhkbs1":t.startsWith("NHKBSプレミアム")?"#nhkbsp":t.startsWith("BS日テレ")?"#bsntv":t.startsWith("BS朝日")?"#bsasahi":t.startsWith("BS-TBS")?"#bstbs":t.startsWith("BSテレ東")?"#bstvtokyo":t.startsWith("BSフジ")?"#bsfuji":t.startsWith("BS11イレブン")?"#bs11":t.startsWith("BS12トゥエルビ")?"#bs12":t.startsWith("AT-X")?"#at_x":null},formatHashtag(t,e=!1){const s=t.trim().replaceAll("♯","#").replaceAll("#","#").replace(/#{2,}/g,"#").replaceAll(" "," ").replaceAll(/ +/g," ").split(" ").filter((t=>""!==t));for(let i in s)s[i].startsWith("#")||(s[i]=`#${s[i]}`);if(!0===this.settingsStore.settings.auto_add_watching_channel_hashtag&&!1===e){const t=this.getChannelHashtag(this.channelsStore.channel.current.channel_name);null!==t&&!1===s.includes(t)&&s.push(t)}return s.join(" ")},async sendTweet(){if(!0===this.is_tweet_sending)return;this.is_tweet_sending=!0,this.tweet_hashtag=this.formatHashtag(this.tweet_hashtag),this.updateTweetLetterCount();const t=this.tweet_hashtag;let e=this.tweet_text;if(""!==t)switch(this.settingsStore.settings.tweet_hashtag_position){case"Prepend":e=`${t} ${this.tweet_text}`;break;case"Append":e=`${this.tweet_text} ${t}`;break;case"PrependWithLineBreak":e=`${t}\n${this.tweet_text}`;break;case"AppendWithLineBreak":e=`${this.tweet_text}\n${t}`;break}const s=[];for(let i of this.tweet_captures)"None"!==this.settingsStore.settings.tweet_capture_watermark_position&&(i=await this.drawProgramTitleOnCapture(i)),s.push(i);Ts.sendTweet(this.selected_twitter_account.screen_name,e,s).then((t=>{this.player.notice(t.message)}));for(const i of this.captures)i.selected=!1,i.focused=!1;this.tweet_captures=[],this.tweet_text="",this.is_tweet_sending=!1,!0===this.settingsStore.settings.fold_panel_after_sending_tweet&&(this.$emit("panel_folding_requested"),this.$refs.tweet_text.blur())}}}),xi=ki,Si=(0,h.Z)(xi,wi,yi,!1,null,"62dbc198",null),Oi=Si.exports,Ti=(s(3767),s(8585),s(8696),s(9908)),ji=s(8488);class Ii{static async uploadCapture(t,e){const s=new FormData;s.append("image",t,e);const i=await G.post("/captures",s,{headers:{"Content-Type":"multipart/form-data"}});if("is_error"in i)switch(i.error.message){case"Permission denied to save the file":K.error("キャプチャのアップロードに失敗しました。保存先フォルダに書き込み権限がありません。");break;case"No space left on the device":K.error("キャプチャのアップロードに失敗しました。保存先フォルダに空き容量がありません。");break;case"Unexpected error occurred while saving the file":K.error("キャプチャのアップロードに失敗しました。保存中に予期しないエラーが発生しました。");break;default:G.showGenericError(i,"キャプチャのアップロードに失敗しました。");break}else;}}var Pi=Ii;let Zi=null,zi=null;class $i{constructor(t,e){this.settings_store=st(),this.player=t,this.player_container=this.player.container,this.captured_callback=e,this.player_container.querySelector(".dplayer-icons.dplayer-icons-right").insertAdjacentHTML("afterbegin",'\n
\n \n \n \n
\n '),this.player_container.querySelector(".dplayer-icons.dplayer-icons-right").insertAdjacentHTML("afterbegin",'\n
\n \n \n \n
\n '),this.comment_capture_button=this.player_container.querySelector(".dplayer-comment-capture-icon"),this.capture_button=this.player_container.querySelector(".dplayer-capture-icon"),this.canvas="OffscreenCanvas"in window?new OffscreenCanvas(0,0):document.createElement("canvas"),this.canvas_context=this.canvas.getContext("2d",{alpha:!1,desynchronized:!0,willReadFrequently:!1}),this.canvas.width=0,this.canvas.height=0,t.on("loadedmetadata",(async()=>{this.canvas.width=t.video.videoWidth,this.canvas.height=t.video.videoHeight;while(0===this.canvas.width&&0===this.canvas.height)await ht.sleep(.1),this.canvas.width=t.video.videoWidth,this.canvas.height=t.video.videoHeight})),(async()=>{const t="https://cdn.jsdelivr.net/npm/noto-sans-japanese@1.0.0/fonts/NotoSansJP-Bold.woff2",e="https://cdn.jsdelivr.net/npm/open-sans-all@0.1.3/fonts/open-sans-700.woff2",s="data:font/woff2;base64,";if(null===Zi){const e=(await G.get(t,{responseType:"arraybuffer"})).data;Zi=s+B.lW.from(e).toString("base64")}if(null===zi){const t=(await G.get(e,{responseType:"arraybuffer"})).data;zi=s+B.lW.from(t).toString("base64")}})()}async captureAndSave(t){var e,s,i,a,n,r,o,l,c,_,d,u;const m=ht.time(),h=Es(),p=h.is_showing_live?h.channel.current:null;if(null!==p&&!0===p.is_radiochannel)return void this.player.notice("ラジオチャンネルはキャプチャできません。");if(0===this.canvas.width&&0===this.canvas.height)return void this.player.notice("読み込み中はキャプチャできません。");if(!0===t&&!1===this.player.danmaku.showing)return void this.player.notice("コメントを付けてキャプチャするには、コメント表示をオンにしてください。");this.addHighlight(t);const g=`Capture_${rt()().format("YYYYMMDD-HHmmss")}`,v=`${g}.jpg`,f=`${g}_caption.jpg`,w=this.player.plugins.aribb24Caption.getRawCanvas(),y=this.player.plugins.aribb24Superimpose.getRawCanvas(),b=!0===this.player.plugins.aribb24Caption.isShowing&&this.player.plugins.aribb24Caption.isPresent(),C=!0===this.player.plugins.aribb24Superimpose.isShowing&&this.player.plugins.aribb24Superimpose.isPresent(),k=b?this.player.plugins.aribb24Caption.getTextContent():null;let x;null!==p&&(x={network_id:p.network_id,service_id:p.service_id,event_id:null!==(s=null===(e=p.program_present)||void 0===e?void 0:e.event_id)&&void 0!==s?s:-1,title:null!==(a=null===(i=p.program_present)||void 0===i?void 0:i.title)&&void 0!==a?a:"放送休止",description:null!==(r=null===(n=p.program_present)||void 0===n?void 0:n.description)&&void 0!==r?r:"",start_time:null!==(l=null===(o=p.program_present)||void 0===o?void 0:o.start_time)&&void 0!==l?l:"2000-01-01T00:00:00+09:00",end_time:null!==(_=null===(c=p.program_present)||void 0===c?void 0:c.end_time)&&void 0!==_?_:"2000-01-01T00:00:00+09:00",duration:null!==(u=null===(d=p.program_present)||void 0===d?void 0:d.duration)&&void 0!==u?u:0,caption_text:k,is_caption_composited:!1,is_comment_composited:!1});const S=async(t,e,s)=>{const i=ht.time();let a;try{a=await this.exportToBlob(t)}catch(n){return console.log(n),this.player.notice("キャプチャの保存に失敗しました…"),!1}return console.log("[CaptureHandler] Export to Blob:",ht.mathFloor(ht.time()-i,3),"sec"),a=await this.setEXIFDataToCapture(a,s),["Browser","Both"].includes(this.settings_store.settings.capture_save_mode)&&ht.downloadBlobData(a,e),["UploadServer","Both"].includes(this.settings_store.settings.capture_save_mode)&&Pi.uploadCapture(a,e),a};let O=null,T=null;const j=await createImageBitmap(this.player.video);if(!1!==t||!1!==C||!1!==b&&"VideoOnly"!==this.settings_store.settings.capture_caption_mode){const e=[];this.canvas_context.drawImage(j,0,0,this.canvas.width,this.canvas.height),!0===C&&this.canvas_context.drawImage(y,0,0,this.canvas.width,this.canvas.height);let s=null;!0===t&&(s=await this.createCommentsImage(),await this.drawComments(s)),(["VideoOnly","Both"].includes(this.settings_store.settings.capture_caption_mode)||!1===b)&&e.push((async()=>{const e="CompositingCaption"===this.settings_store.settings.capture_caption_mode?f:v,s=await S(this.canvas,e,Object.assign(Object.assign({},x),{is_caption_composited:!1,is_comment_composited:t}));O=!1!==s&&{blob:s,filename:e},!1!==O&&this.captured_callback(O.blob,O.filename)})()),["CompositingCaption","Both"].includes(this.settings_store.settings.capture_caption_mode)&&!0===b&&e.push((async()=>{!0===t&&(this.canvas_context.drawImage(j,0,0,this.canvas.width,this.canvas.height),!0===C&&this.canvas_context.drawImage(y,0,0,this.canvas.width,this.canvas.height)),j.close(),this.canvas_context.drawImage(w,0,0,this.canvas.width,this.canvas.height),!0===t&&await this.drawComments(s);const e=await S(this.canvas,f,Object.assign(Object.assign({},x),{is_caption_composited:!0,is_comment_composited:t}));if(T=!1!==e&&{blob:e,filename:f},!1!==T){if("Both"===this.settings_store.settings.capture_caption_mode)while(null===O)await ht.sleep(.01);this.captured_callback(T.blob,T.filename)}})()),await Promise.all(e)}else{const t="OffscreenCanvas"in window?new OffscreenCanvas(j.width,j.height):document.createElement("canvas");t.width=j.width,t.height=j.height;const e=t.getContext("bitmaprenderer",{alpha:!1});e.transferFromImageBitmap(j),j.close();const s="CompositingCaption"===this.settings_store.settings.capture_caption_mode?f:v,i=await S(t,s,Object.assign(Object.assign({},x),{is_caption_composited:!1,is_comment_composited:!1}));O=!1!==i&&{blob:i,filename:s},!1!==O&&this.captured_callback(O.blob,O.filename)}console.log("[CaptureHandler] Total:",ht.mathFloor(ht.time()-m,3),"sec"),this.removeHighlight(t);for(const P of[O,T])if(this.settings_store.settings.capture_copy_to_clipboard&&null!==P&&!1!==P)try{await(0,Ti.FH)(await(0,Ti.BD)(P.blob))}catch(I){this.player.notice("クリップボードへのキャプチャのコピーに失敗しました…"),console.error(I)}}addHighlight(t=!1){t?this.comment_capture_button.classList.add("dplayer-capturing"):this.capture_button.classList.add("dplayer-capturing")}removeHighlight(t=!1){t?this.comment_capture_button.classList.remove("dplayer-capturing"):this.capture_button.classList.remove("dplayer-capturing")}async commentsHTMLtoSVGImage(t,e,s){const i=`\n \n \n
\n \n ${t}\n
\n
\n
\n `.trim(),a=new Image;return a.src=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(i)}`,await a.decode(),a}async createCommentsImage(){let t=this.player.template.danmaku.outerHTML;for(const e of this.player_container.querySelectorAll(".dplayer-danmaku-move")){const s=e.getBoundingClientRect().left-this.player.video.getBoundingClientRect().left;t=t.replace(/transform: translateX\(.*?\);/,`left: ${s}px;`).replaceAll("border: 2px solid #E64F97;","")}return await this.commentsHTMLtoSVGImage(t,this.player.template.danmaku.offsetWidth,this.player.template.danmaku.offsetHeight)}async drawComments(t){const e=this.canvas.width/this.player.template.danmaku.offsetWidth,s=this.player.template.danmaku.offsetHeight*e;this.canvas_context.drawImage(t,0,0,this.canvas.width,s)}async exportToBlob(t){return"OffscreenCanvas"in window&&t instanceof OffscreenCanvas?await t.convertToBlob({type:"image/jpeg",quality:.99}):t instanceof HTMLCanvasElement?new Promise(((e,s)=>{t.toBlob((t=>{null!==t?e(t):s(new Error("Failed to convert canvas to blob"))}),"image/jpeg",.99)})):void 0}async setEXIFDataToCapture(t,e){const s=rt()().diff(rt()(e.start_time),"second",!0),i={captured_at:rt()().format("YYYY-MM-DDTHH:mm:ss+09:00"),captured_playback_position:s,network_id:e.network_id,service_id:e.service_id,event_id:e.event_id,title:e.title,description:e.description,start_time:e.start_time,end_time:e.end_time,duration:e.duration,caption_text:e.caption_text,is_caption_composited:e.is_caption_composited,is_comment_composited:e.is_comment_composited},a=rt()().format("YYYY:MM:DD HH:mm:ss"),n={"0th":{[ji.TagValues.ImageIFD.XResolution]:[72,1],[ji.TagValues.ImageIFD.YResolution]:[72,1],[ji.TagValues.ImageIFD.ResolutionUnit]:2,[ji.TagValues.ImageIFD.YCbCrPositioning]:1,[ji.TagValues.ImageIFD.DateTime]:a,[ji.TagValues.ImageIFD.Software]:`KonomiTV version ${ht.version}`,[ji.TagValues.ImageIFD.XPComment]:[...B.lW.from(JSON.stringify(i),"ucs2")]},Exif:{[ji.TagValues.ExifIFD.ExifVersion]:"0230",[ji.TagValues.ExifIFD.ComponentsConfiguration]:"\0",[ji.TagValues.ExifIFD.FlashpixVersion]:"0100",[ji.TagValues.ExifIFD.ColorSpace]:1,[ji.TagValues.ExifIFD.DateTimeOriginal]:a,[ji.TagValues.ExifIFD.DateTimeDigitized]:a}},r=ji.dump(n),o=await new Promise(((e,s)=>{const i=new FileReader;i.onload=()=>e(i.result),i.onerror=s,i.readAsBinaryString(t)})),l=ji.insert(r,o),c=new Uint8Array(l.length);for(let _=0;_{const t=st();switch(t.settings.panel_display_state){case"AlwaysDisplay":return!0;case"AlwaysFold":return!1;case"RestorePreviousState":return t.settings.showed_panel_last_time}})(),is_fullscreen:!1,is_ime_composing:!1,is_virtual_keyboard_display:!1,is_comment_send_just_did:!1,interval_ids:[],control_interval_id:0,is_zapping:!1,is_zapping_continuously:!1,player:null,player_can_be_destroyed:!1,is_mpegts_supported:!0===Js().isSupported(),romsounds_context:null,romsounds_buffers:[],eventsource:null,fullscreen_handler:null,capture_handler:null,shortcut_key_handler:null,shortcut_key_pressed_at:ht.time(),shortcut_key_modal:!1,shortcut_key_list:{left_column:[{name:"全般",icon:"fluent:home-20-filled",icon_height:"22px",shortcuts:[{name:"数字キー/テンキーに対応するリモコン番号 (1~12) の地デジチャンネルに切り替える",keys:[{name:"1~9, 0, -(=), ^(~)",icon:!1}]},{name:"数字キー/テンキーに対応するリモコン番号 (1~12) の BS チャンネルに切り替える",keys:[{name:"Shift",icon:!1},{name:"1~9, 0, -(=), ^(~)",icon:!1}]},{name:"前のチャンネルに切り替える",keys:[{name:"fluent:arrow-up-12-filled",icon:!0}]},{name:"次のチャンネルに切り替える",keys:[{name:"fluent:arrow-down-12-filled",icon:!0}]},{name:"キーボードショートカットの一覧を表示する",keys:[{name:"/(?)",icon:!1}]}]},{name:"プレイヤー",icon:"fluent:play-20-filled",icon_height:"20px",shortcuts:[{name:"再生 / 一時停止の切り替え",keys:[{name:"Space",icon:!1}]},{name:"再生 / 一時停止の切り替え (キャプチャタブ表示時)",keys:[{name:"Shift",icon:!1},{name:"Space",icon:!1}]},{name:"プレイヤーの音量を上げる",keys:[{name:ht.CtrlOrCmd(),icon:!1},{name:"fluent:arrow-up-12-filled",icon:!0}]},{name:"プレイヤーの音量を下げる",keys:[{name:ht.CtrlOrCmd(),icon:!1},{name:"fluent:arrow-down-12-filled",icon:!0}]},{name:"停止して0.5秒早戻し",keys:[{name:ht.CtrlOrCmd(),icon:!1},{name:"fluent:arrow-left-12-filled",icon:!0}]},{name:"停止して0.5秒早送り",keys:[{name:ht.CtrlOrCmd(),icon:!1},{name:"fluent:arrow-right-12-filled",icon:!0}]},{name:"フルスクリーンの切り替え",keys:[{name:"F",icon:!1}]},{name:"ライブストリームの同期",keys:[{name:"W",icon:!1}]},{name:"Picture-in-Picture の表示切り替え",keys:[{name:"E",icon:!1}]},{name:"字幕の表示切り替え",keys:[{name:"S",icon:!1}]},{name:"コメントの表示切り替え",keys:[{name:"D",icon:!1}]},{name:"映像をキャプチャする",keys:[{name:"C",icon:!1}]},{name:"映像をコメントを付けてキャプチャする",keys:[{name:"V",icon:!1}]},{name:"コメント入力フォームにフォーカスする",keys:[{name:"M",icon:!1}]},{name:"コメント入力フォームを閉じる",keys:[{name:ht.CtrlOrCmd(),icon:!1},{name:"M",icon:!1}]}]}],right_column:[{name:"パネル",icon:"fluent:panel-right-20-filled",icon_height:"24px",shortcuts:[{name:"パネルの表示切り替え",keys:[{name:"P",icon:!1}]},{name:"番組情報タブを表示する",keys:[{name:"K",icon:!1}]},{name:"チャンネルタブを表示する",keys:[{name:"L",icon:!1}]},{name:"コメントタブを表示する",keys:[{name:";(+)",icon:!1}]},{name:"Twitter タブを表示する",keys:[{name:":(*)",icon:!1}]}]},{name:"Twitter",icon:"fa-brands:twitter",icon_height:"22px",shortcuts:[{name:"ツイート検索タブを表示する",keys:[{name:"[ (「)",icon:!1}]},{name:"タイムラインタブを表示する",keys:[{name:"] (」)",icon:!1}]},{name:"キャプチャタブを表示する",keys:[{name:"\(¥)",icon:!1}]},{name:"ツイート入力フォームにフォーカスを当てる/フォーカスを外す",keys:[{name:"Tab",icon:!1}]},{name:"キャプチャにフォーカスする",keys:[{name:"キャプチャタブを表示",icon:!1},{name:"fluent:arrow-up-12-filled;fluent:arrow-down-12-filled;fluent:arrow-left-12-filled;fluent:arrow-right-12-filled",icon:!0}]},{name:"キャプチャを拡大表示する/
キャプチャの拡大表示を閉じる",keys:[{name:"キャプチャにフォーカス",icon:!1},{name:"Enter",icon:!1}]},{name:"キャプチャを選択する/
キャプチャの選択を解除する",keys:[{name:"キャプチャにフォーカス",icon:!1},{name:"Space",icon:!1}]},{name:"クリップボード内の画像を
キャプチャとして取り込む",keys:[{name:"ツイート入力
フォームにフォーカス",icon:!1},{name:ht.CtrlOrCmd(),icon:!1},{name:"V",icon:!1}]},{name:"ツイートを送信する",keys:[{name:"Twitter タブを表示",icon:!1},{name:ht.CtrlOrCmd(),icon:!1},{name:"Enter",icon:!1}]}]}]}}},computed:Object.assign({},(0,a.Kc)(Es,st)),async created(){!0===this.settingsStore.settings.tv_channel_selection_requires_alt_key&&(this.shortcut_key_list.left_column[0].shortcuts[0].keys.unshift({name:ht.AltOrOption(),icon:!1}),this.shortcut_key_list.left_column[0].shortcuts[1].keys.unshift({name:ht.AltOrOption(),icon:!1})),this.channelsStore.channel_id=this.$route.params.channel_id,"virtualKeyboard"in navigator&&(navigator.virtualKeyboard.overlaysContent=!0,navigator.virtualKeyboard.ongeometrychange=t=>{0===t.target.boundingRect.width&&0===t.target.boundingRect.height?this.is_virtual_keyboard_display=!1:this.is_virtual_keyboard_display=!0}),this.init(),this.romsounds_context=new AudioContext;for(let t=1;t<=14;t++){const e=`/assets/romsounds/${t.toString().padStart(2,"0")}.wav`,s=await G.get(e,{baseURL:"",responseType:"arraybuffer"});this.romsounds_buffers.push(await this.romsounds_context.decodeAudioData(s.data))}},beforeDestroy(){"virtualKeyboard"in navigator&&(navigator.virtualKeyboard.overlaysContent=!1),"gr999"!==this.channelsStore.channel.current.channel_id&&this.destroy(!0),null!==this.romsounds_context&&this.romsounds_context.close(),this.channelsStore.channel_id="gr000"},beforeRouteUpdate(t,e,s){const i=this.destroy(!1,this.is_zapping_continuously);this.is_zapping_continuously=!0,this.channelsStore.channel_id=t.params.channel_id,!0===this.settingsStore.settings.reset_hashtag_when_program_switches&&(this.$refs.Twitter.tweet_hashtag=""),(async()=>{!0===this.is_zapping?(this.is_zapping=!1,this.interval_ids.push(window.setTimeout((()=>{this.is_zapping_continuously=!1,i.then((()=>this.init()))}),500))):(this.is_zapping_continuously=!1,i.then((()=>this.init())))})(),s()},watch:{"channelsStore.channel":{immediate:!0,handler(t,e){var s;if(void 0===e)return;const i=this.$refs.Twitter;if(t.current.channel_id!==e.current.channel_id){const t=null!==(s=i.getChannelHashtag(e.current.channel_name))&&void 0!==s?s:"";i.tweet_hashtag=i.formatHashtag(i.tweet_hashtag.replaceAll(t,"")),i.updateTweetLetterCount()}(t.current.id!==e.current.id||null!==t.current.program_present&&null===e.current.program_present||null===t.current.program_present&&null!==e.current.program_present||null!==t.current.program_present&&null!==e.current.program_present&&t.current.program_present.id!==e.current.program_present.id)&&!0===this.settingsStore.settings.reset_hashtag_when_program_switches&&(i.tweet_hashtag=i.formatHashtag(""),i.updateTweetLetterCount())}},is_panel_display(){this.settingsStore.settings.showed_panel_last_time=this.is_panel_display}},methods:{async init(){this.background_url=at.generatePlayerBackgroundURL(),this.controlDisplayTimer(),this.interval_ids.push(window.setInterval((()=>this.time=rt()().format("YYYY/MM/DD HH:mm:ss")),1e3));const t=60-(new Date).getSeconds();this.interval_ids.push(window.setTimeout((()=>{this.channelsStore.update(!0),this.update(),this.interval_ids.push(window.setInterval((()=>{this.channelsStore.update(!0),this.update()}),3e4))}),1e3*t)),await this.channelsStore.update(),this.update()},async update(){var t,e;if(void 0!==this.$route.params.channel_id)if("gr999"!==this.channelsStore.channel.current.channel_id){if(null!==this.player&&!0!==this.player_can_be_destroyed||(this.initPlayer(),this.initEventHandler(),this.initCaptureHandler(),document.removeEventListener("keydown",this.shortcut_key_handler),this.initShortcutKeyHandler()),null!==this.player){if(null===this.channelsStore.channel.current.program_present||"1/0+1/0モード(デュアルモノ)"!==this.channelsStore.channel.current.program_present.primary_audio_type&&null===this.channelsStore.channel.current.program_present.secondary_audio_type){if(this.player.template.audioItem[1].classList.add("dplayer-setting-audio-item--disabled"),void 0!==this.player.plugins.mpegts||void 0!==this.player.plugins.liveLLHLSForKonomiTV){while(null===this.player)await ht.sleep(.1);this.player.template.audioItem[0].classList.add("dplayer-setting-audio-current"),this.player.template.audioItem[1].classList.remove("dplayer-setting-audio-current"),this.player.template.audioValue.textContent=this.player.tran("Primary audio");try{void 0!==this.player.plugins.mpegts&&this.player.plugins.mpegts instanceof Js().MSEPlayer&&this.player.plugins.mpegts.switchPrimaryAudio(),void 0!==this.player.plugins.liveLLHLSForKonomiTV&&this.player.plugins.liveLLHLSForKonomiTV.switchPrimaryAudio()}catch(s){}}}else this.player.template.audioItem[1].classList.remove("dplayer-setting-audio-item--disabled");if("mediaSession"in navigator){const s=[{src:"/assets/images/icons/icon-maskable-192px.png",sizes:"192x192",type:"image/png"},{src:"/assets/images/icons/icon-maskable-512px.png",sizes:"512x512",type:"image/png"}];navigator.mediaSession.metadata=new MediaMetadata({title:null!==(e=null===(t=this.channelsStore.channel.current.program_present)||void 0===t?void 0:t.title)&&void 0!==e?e:"放送休止",artist:this.channelsStore.channel.current.channel_name,artwork:s}),"setPositionState"in navigator.mediaSession&&navigator.mediaSession.setPositionState({duration:0,playbackRate:1}),navigator.mediaSession.setActionHandler("play",null),navigator.mediaSession.setActionHandler("pause",null),navigator.mediaSession.setActionHandler("previoustrack",null),navigator.mediaSession.setActionHandler("nexttrack",null),navigator.mediaSession.setActionHandler("play",(()=>{var t;return null===(t=this.player)||void 0===t?void 0:t.play()})),navigator.mediaSession.setActionHandler("pause",(()=>{var t;return null===(t=this.player)||void 0===t?void 0:t.pause()})),navigator.mediaSession.setActionHandler("previoustrack",(async()=>{var t,e;navigator.mediaSession.metadata=new MediaMetadata({title:null!==(e=null===(t=this.channelsStore.channel.previous.program_present)||void 0===t?void 0:t.title)&&void 0!==e?e:"放送休止",artist:this.channelsStore.channel.previous.channel_name,artwork:s}),await this.$router.push({path:`/tv/watch/${this.channelsStore.channel.previous.channel_id}`})})),navigator.mediaSession.setActionHandler("nexttrack",(async()=>{var t,e;navigator.mediaSession.metadata=new MediaMetadata({title:null!==(e=null===(t=this.channelsStore.channel.next.program_present)||void 0===t?void 0:t.title)&&void 0!==e?e:"放送休止",artist:this.channelsStore.channel.next.channel_name,artwork:s}),await this.$router.push({path:`/tv/watch/${this.channelsStore.channel.next.channel_id}`})}))}}}else this.$router.push({path:"/not-found/"})},controlDisplayTimer(t=null,e=!1){var s,i,a;const n=/iPhone|iPad|iPod|Windows|Macintosh|Android|Mobile/i.test(navigator.userAgent)&&"ontouchend"in document;if(1==n&&null!==t&&"mousemove"===t.type)return;if(0==n&&null!==t&&("touchmove"===t.type||"click"===t.type))return;window.clearTimeout(this.control_interval_id);const r=()=>{null!==this.player&&this.player.template.controller.classList.contains("dplayer-controller-comment")?this.control_interval_id=window.setTimeout(r,3e3):(this.is_control_display=!1,null!==this.player&&(this.player.controller.hide(),this.player.setting.hide()))};!0===n&&!0===e?(null===(s=this.player)||void 0===s?void 0:s.controller.isShow())?(this.is_control_display=!0,this.player.controller.show(),this.control_interval_id=window.setTimeout(r,3e3)):(this.is_control_display=!1,null===(i=this.player)||void 0===i||i.controller.hide(),null===(a=this.player)||void 0===a||a.setting.hide()):(this.is_control_display=!0,null!==this.player&&this.player.controller.show(),this.control_interval_id=window.setTimeout(r,3e3))},initPlayer(){if(window.mpegts=Js(),null!==this.player&&!0===this.player_can_be_destroyed){try{this.player.destroy()}catch(a){void 0!==this.player.plugins.mpegts&&this.player.plugins.mpegts.destroy()}this.player_can_be_destroyed=!1,this.player=null}const t=this.settingsStore.settings.tv_low_latency_mode?Di:Bi;this.player=new(Xs())({container:this.$el.querySelector(".watch-player__dplayer"),theme:"#E64F97",lang:"ja-jp",live:!0,liveSyncMinBufferSize:this.is_mpegts_supported?t-.1:0,loop:!1,airplay:!1,autoplay:!0,hotkey:!1,screenshot:!1,volume:1,video:{defaultQuality:this.channelsStore.channel.current.is_radiochannel?"48kHz/192kbps":this.settingsStore.settings.tv_streaming_quality,quality:(()=>{const t=[];if(this.channelsStore.channel.current.is_radiochannel)!0===this.is_mpegts_supported?t.push({name:"48kHz/192kbps",type:"mpegts",url:`${ht.api_base_url}/streams/live/${this.channelsStore.channel_id}/1080p/mpegts`}):t.push({name:"48kHz/192kbps",type:"live-llhls-for-KonomiTV",url:`${ht.api_base_url}/streams/live/${this.channelsStore.channel_id}/1080p/ll-hls`});else{let e="";at.isHEVCVideoSupported()&&!0===this.settingsStore.settings.tv_data_saver_mode&&(e="-hevc");for(const s of["1080p-60fps","1080p","810p","720p","540p","480p","360p","240p"])!0===this.is_mpegts_supported?t.push({name:"1080p-60fps"===s?"1080p (60fps)":s,type:"mpegts",url:`${ht.api_base_url}/streams/live/${this.channelsStore.channel_id}/${s}${e}/mpegts`}):t.push({name:"1080p-60fps"===s?"1080p (60fps)":s,type:"live-llhls-for-KonomiTV",url:`${ht.api_base_url}/streams/live/${this.channelsStore.channel_id}/${s}${e}/ll-hls`})}return t})()},danmaku:{user:"KonomiTV",speedRate:this.settingsStore.settings.comment_speed_rate,fontSize:this.settingsStore.settings.comment_font_size},apiBackend:{read:t=>{t.success([])},send:async t=>{this.$refs.Comment.sendComment(t)}},pluginOptions:{mpegts:{config:{enableWorker:!0,enableStashBuffer:!1,liveSync:this.settingsStore.settings.tv_low_latency_mode,liveSyncMaxLatency:3,liveSyncTargetLatency:t,liveSyncPlaybackRate:1.1}},aribb24:{normalFont:`"${this.settingsStore.settings.caption_font}", "Rounded M+ 1m for ARIB", sans-serif`,forceStrokeColor:this.settingsStore.settings.always_border_caption_text,forceBackgroundColor:(()=>{if(!0===this.settingsStore.settings.specify_caption_opacity){const t=this.settingsStore.settings.caption_opacity;return`rgba(0, 0, 0, ${t})`}return null})(),drcsReplacement:!0,enableRawCanvas:!0,useStroke:!0,usePUA:(()=>{const t=this.settingsStore.settings.caption_font,e=document.createElement("canvas").getContext("2d");return e.font='10px "Rounded M+ 1m for ARIB"',e.fillText("Test",0,0),e.font=`10px "${t}"`,e.fillText("Test",0,0),!!t.startsWith("Windows TV")})(),PRACallback:async t=>{if(!1===this.settingsStore.settings.tv_show_superimpose)return;"suspended"===this.romsounds_context.state&&await this.romsounds_context.resume();const e=this.romsounds_context.createBufferSource();e.buffer=this.romsounds_buffers[t];const s=this.romsounds_context.createGain();e.connect(s),s.connect(this.romsounds_context.destination),s.gain.value=3,e.start(0)}}},subtitle:{type:"aribb24"}}),window.player=this.player,this.player.controller.setAutoHide=t=>{},this.$refs.Comment.initSession(this.player,this.channelsStore.channel_id),this.player.template.commentInput.addEventListener("keydown",(t=>{"Enter"===t.code&&(this.is_comment_send_just_did=!0,setTimeout((()=>this.is_comment_send_just_did=!1),100))})),this.player.comment.send=()=>{null!==this.player&&(!0===this.settingsStore.settings.close_comment_form_after_sending&&this.player.template.commentInput.blur(),this.player.template.commentInput.value.replace(/^\s+|\s+$/g,"")?(this.player.danmaku.send({text:this.player.template.commentInput.value,color:this.player.container.querySelector(".dplayer-comment-setting-color input:checked").value,type:this.player.container.querySelector(".dplayer-comment-setting-type input:checked").value,size:this.player.container.querySelector(".dplayer-comment-setting-size input:checked").value},(()=>{!0===this.settingsStore.settings.close_comment_form_after_sending&&null!==this.player&&this.player.comment.hide()}),!0),this.player.template.commentInput.value=""):this.player.notice(this.player.tran("Please input danmaku content!")))};const e=/iPhone|iPad|iPod|Macintosh|Android|Mobile/i.test(navigator.userAgent)&&"ontouchend"in document;if(!1===e){this.player.template.settingOriginPanel.insertAdjacentHTML("beforeend",'\n
\n キーボードショートカット\n
\n \n \n \n
\n
');const t=this.player.template.settingOriginPanel.scrollHeight;this.player.template.settingBox.style.clipPath=`inset(calc(100% - ${t}px) 0 0 round 7px)`,this.$el.querySelector(".dplayer-setting-keyboard-shortcut").addEventListener("click",(()=>{var t;null===(t=this.player)||void 0===t||t.setting.hide(),this.shortcut_key_modal=!0}))}const s=document.querySelector(".v-application");this.fullscreen_handler=()=>{var t;this.is_fullscreen=!0===(null===(t=this.player)||void 0===t?void 0:t.fullScreen.isFullScreen())},void 0!==s.onfullscreenchange?s.addEventListener("fullscreenchange",this.fullscreen_handler):s.addEventListener("webkitfullscreenchange",this.fullscreen_handler),this.player.fullScreen.isFullScreen=t=>!(!document.fullscreenElement&&!document.webkitFullscreenElement),this.player.fullScreen.request=t=>{null!==this.player&&(this.player.fullScreen.isFullScreen()?this.player.fullScreen.cancel():(s.requestFullscreen=s.requestFullscreen||s.webkitRequestFullscreen,s.requestFullscreen?(s.requestFullscreen(),screen.orientation&&screen.orientation.lock("landscape").catch((()=>{}))):this.player.notice("iPhone Safari は動画のフルスクリーン表示に対応していません。")))},this.player.fullScreen.cancel=t=>{document.exitFullscreen=document.exitFullscreen||document.webkitExitFullscreen,document.exitFullscreen&&document.exitFullscreen(),screen.orientation&&screen.orientation.unlock()};const i=()=>{var t;null===(t=this.player)||void 0===t||t.setting.hide(),this.controlDisplayTimer()};this.player.on("play",i),this.player.on("pause",i),this.player.on("quality_start",(()=>{this.background_url=at.generatePlayerBackgroundURL(),null!==this.eventsource&&(this.eventsource.close(),this.eventsource=null),this.initEventHandler()})),!0===this.is_mpegts_supported?this.interval_ids.push(window.setInterval((()=>{null!==this.player&&this.player.video.paused&&this.player.video.buffered.length>=1&&this.player.video.buffered.end(0)-this.player.video.currentTime>30&&this.player.sync()}),6e4)):this.interval_ids.push(window.setInterval((()=>{null!==this.player&&this.player.video.paused&&this.player.sync()}),3e4)),(async()=>{if(null!==this.player){while(void 0===this.player.plugins.aribb24Superimpose)await ht.sleep(.1);!0===this.settingsStore.settings.tv_show_superimpose?(this.player.plugins.aribb24Superimpose.show(),this.player.on("subtitle_hide",(()=>{var t;null===(t=this.player)||void 0===t||t.plugins.aribb24Superimpose.show()}))):(this.player.plugins.aribb24Superimpose.hide(),this.player.on("subtitle_show",(()=>{var t;null===(t=this.player)||void 0===t||t.plugins.aribb24Superimpose.hide()})))}})()},initEventHandler(){if(null===this.player)return;if(this.is_loading=!0,this.player.video.volume=0,this.player.video.crossOrigin="anonymous",!0===this.is_mpegts_supported&&void 0!==this.player.plugins.mpegts?this.player.plugins.mpegts.on(Js().Events.ERROR,(async(t,e)=>{this.player.notice(`再生中にエラーが発生しました。(Type: ${t}) 3秒後にリロードします。`,-1),await ht.sleep(3),location.reload()})):!1===this.is_mpegts_supported&&this.player.on("error",(async()=>{this.player.notice(`再生中にエラーが発生しました。(Type: ${this.player.video.error.message}) 3秒後にリロードします。`,-1),await ht.sleep(3),location.reload()})),!1===this.is_mpegts_supported){const t=()=>{var e,s;null===(e=this.player)||void 0===e||e.video.play().catch((()=>{console.warn("HTMLVideoElement.play() rejected. run fallback.");const t='';this.player.template.playButton.innerHTML=t,this.player.template.mobilePlayButton.innerHTML=t,this.player.container.classList.remove("dplayer-paused"),this.player.container.classList.add("dplayer-playing"),this.player.danmaku.play()})),!1!==this.is_loading||null===(s=this.player)||void 0===s||s.video.removeEventListener("pause",t)};this.player.video.addEventListener("pause",t)}const t=async()=>{if(null===this.player)return;if(this.player.video.oncanplay=null,this.player.video.oncanplaythrough=null,!0===this.is_mpegts_supported){this.player.video.playbackRate=0;const t=()=>{let t=0;return this.player.video.buffered.length>=1&&(t=this.player.video.buffered.end(0)),Math.round(1e3*(t-this.player.video.currentTime))/1e3},e=this.settingsStore.settings.tv_low_latency_mode?Di:Bi;let s=t();while(s{var t,e,s;await ht.sleep(.5),4!==(null===(t=this.player)||void 0===t?void 0:t.video.readyState)&&(console.log("player.video.readyState !== ENOUGH_DATA. trying to recover."),null===(e=this.player)||void 0===e||e.video.pause(),await ht.sleep(.1),null===(s=this.player)||void 0===s||s.video.play().catch((()=>{var t;console.warn("HTMLVideoElement.play() rejected. paused."),null===(t=this.player)||void 0===t||t.pause()})))};this.player.video.addEventListener("waiting",(()=>this.is_video_buffering=!0)),this.player.video.addEventListener("playing",(()=>{this.is_video_buffering=!1,t()})),this.is_loading=!1,this.is_video_buffering=!1,t(),this.channelsStore.channel.current.is_radiochannel?this.is_background_display=!0:this.is_background_display=!1;const e=this.player.user.get("volume");while(this.player.video.volume+.05{const e=JSON.parse(t.data);switch(console.log(`[initial_update] Status: ${e.status} / Detail: ${e.detail}`),e.status){case"Standby":this.is_video_buffering=!0,this.is_background_display=!0;break}})),this.eventsource.addEventListener("status_update",(async e=>{var s,i;if(null===this.player)return;const a=JSON.parse(e.data);switch(console.log(`[status_update] Status: ${a.status} / Detail: ${a.detail}`),this.channelsStore.updateChannel(this.channelsStore.channel_id,Object.assign(Object.assign({},this.channelsStore.channel.current),{viewers:a.clients_count})),a.status){case"Standby":this.player.template.notice.textContent.includes("画質を")||this.player.notice(a.detail,-1),this.is_video_buffering=!0,this.is_background_display=!0;break;case"ONAir":this.player.template.notice.textContent.includes("画質を")||this.player.notice(this.player.template.notice.textContent,1e-6),!1===this.is_mpegts_supported&&(this.player.video.load(),this.player.video.play(),t()),this.player.container.classList.contains("dplayer-paused")&&(this.player.container.classList.remove("dplayer-paused"),this.player.container.classList.add("dplayer-playing")),document.pictureInPictureElement&&(document.exitPictureInPicture(),this.player.video.requestPictureInPicture());break;case"Idling":this.player.notice("ストリーミング接続が切断されました。3秒後にリロードします。",-1),await ht.sleep(3),location.reload();break;case"Restart":"ライブストリームは Offline です。"===a.detail&&(this.player.notice("ストリーミング接続が切断されました。3秒後にリロードします。",-1),await ht.sleep(3),location.reload()),this.player.notice(a.detail,-1),this.player.switchVideo({url:this.player.quality.url,type:this.player.quality.type}),this.player.play(),this.is_video_buffering=!0,this.is_background_display=!0;break;case"Offline":null!==this.player&&(this.player.notice(a.detail,-1),this.player.video.onerror=()=>{this.player.notice(a.detail,-1),this.player.video.onerror=null},null===(i=null===(s=this.player)||void 0===s?void 0:s.danmaku)||void 0===i||i.clear(),this.player.video.pause()),null!==this.eventsource&&(this.eventsource.close(),this.eventsource=null),this.is_background_display=!0,this.is_loading=!1,this.is_video_buffering=!1;break}})),this.eventsource.addEventListener("detail_update",(t=>{if(null===this.player)return;const e=JSON.parse(t.data);switch(console.log(`[detail_update] Status: ${e.status} Detail:${e.detail}`),this.channelsStore.updateChannel(this.channelsStore.channel_id,Object.assign(Object.assign({},this.channelsStore.channel.current),{viewers:e.clients_count})),e.status){case"Standby":this.player.notice(e.detail,-1),this.is_background_display||(this.is_background_display=!0);break}})),this.eventsource.addEventListener("clients_update",(t=>{const e=JSON.parse(t.data);this.channelsStore.updateChannel(this.channelsStore.channel_id,Object.assign(Object.assign({},this.channelsStore.channel.current),{viewers:e.clients_count}))}))},initShortcutKeyHandler(){const t=this.$refs.Twitter,e=t.$el.querySelector(".tweet-form__textarea");for(const s of document.querySelectorAll("input[type=text],input[type=search],textarea"))s.addEventListener("compositionstart",(()=>this.is_ime_composing=!0)),s.addEventListener("compositionend",(()=>this.is_ime_composing=!1));this.shortcut_key_handler=async s=>{const i=document.activeElement.tagName.toUpperCase(),a=document.activeElement.getAttribute("contenteditable");["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(s.code)&&"INPUT"!==i&&"TEXTAREA"!==i&&""!==a&&"true"!==a&&s.preventDefault();let n=!1;s.repeat&&(n=!0);const r=ht.time();if(r-this.shortcut_key_pressed_at<.05)return;this.shortcut_key_pressed_at=r;const o=await(async()=>{if(("INPUT"!==i&&"TEXTAREA"!==i&&""!==a&&"true"!==a||document.activeElement===e)&&!1===this.is_ime_composing&&"Tab"===s.code)return document.activeElement===e?(e.blur(),!0):(this.is_panel_display=!0,this.tv_panel_active_tab="Twitter",e.focus(),this.$el.scrollLeft=0,window.setTimeout((()=>{e.focus(),this.$el.scrollLeft=0}),100),!0);if(("INPUT"!==i&&"TEXTAREA"!==i&&""!==a&&"true"!==a||document.activeElement===e)&&"Twitter"===this.tv_panel_active_tab&&!1===this.is_ime_composing&&(s.ctrlKey||s.metaKey||s.shiftKey)&&"Enter"===s.code)return t.$el.querySelector(".tweet-button").click(),!0;if(null!==this.player&&!s.shiftKey&&!s.altKey&&this.player.template.controller.classList.contains("dplayer-controller-comment")&&(s.ctrlKey||s.metaKey)&&"KeyM"===s.code)return this.player.comment.hide(),!0;if("INPUT"!==i&&"TEXTAREA"!==i&&""!==a&&"true"!==a){if(!1===n&&!s.ctrlKey&&!s.metaKey&&(!1===this.settingsStore.settings.tv_channel_selection_requires_alt_key||s.altKey)){const t=s.shiftKey?"BS":"GR";let e=null;if("Digit1"!==s.code&&"Digit2"!==s.code&&"Digit3"!==s.code&&"Digit4"!==s.code&&"Digit5"!==s.code&&"Digit6"!==s.code&&"Digit7"!==s.code&&"Digit8"!==s.code&&"Digit9"!==s.code||(e=Number(s.code.replace("Digit",""))),"Digit0"===s.code&&(e=10),"Minus"===s.code&&(e=11),"Equal"===s.code&&(e=12),"Numpad1"!==s.code&&"Numpad2"!==s.code&&"Numpad3"!==s.code&&"Numpad4"!==s.code&&"Numpad5"!==s.code&&"Numpad6"!==s.code&&"Numpad7"!==s.code&&"Numpad8"!==s.code&&"Numpad9"!==s.code||(e=Number(s.code.replace("Numpad",""))),"Numpad0"===s.code&&(e=10),null!==e){const s=this.channelsStore.getChannelByRemoconID(t,e);if(null!==s&&s.channel_id!==this.channelsStore.channel_id)return await this.$router.push({path:`/tv/watch/${s.channel_id}`}),!0}}if(!1===n&&!s.ctrlKey&&!s.metaKey&&!s.shiftKey&&!s.altKey){if("Slash"===s.code)return this.shortcut_key_modal=!this.shortcut_key_modal,!0;if("KeyP"===s.code)return this.is_panel_display=!this.is_panel_display,!0;if("KeyK"===s.code)return this.tv_panel_active_tab="Program",!0;if("KeyL"===s.code)return this.tv_panel_active_tab="Channel",!0;if("Semicolon"===s.code)return this.tv_panel_active_tab="Comment",!0;if("Quote"===s.code)return this.tv_panel_active_tab="Twitter",!0;if("BracketRight"===s.code)return t.twitter_active_tab="Search",!0;if("Backslash"===s.code)return t.twitter_active_tab="Timeline",!0;if("IntlRo"===s.code)return t.twitter_active_tab="Capture",!0}if("Twitter"===this.tv_panel_active_tab&&"Capture"===t.twitter_active_tab&&!s.ctrlKey&&!s.metaKey&&!s.shiftKey&&!s.altKey){if(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(s.code)){if(0===t.captures.length)return!1;if(!1===t.captures.some((t=>!0===t.focused)))return t.captures[t.captures.length-1].focused=!0,!0;const e=t.captures.findIndex((t=>!0===t.focused));if("ArrowUp"===s.code){if(e-2<0)return!1;t.captures[e-2].focused=!0}if("ArrowDown"===s.code){if(e+2>t.captures.length-1)return!1;t.captures[e+2].focused=!0}if("ArrowLeft"===s.code){if(e-1<0)return!1;t.captures[e-1].focused=!0}if("ArrowRight"===s.code){if(e+1>t.captures.length-1)return!1;t.captures[e+1].focused=!0}t.captures[e].focused=!1;const i=t.captures.find((t=>!0===t.focused));!0===t.zoom_capture_modal&&(t.zoom_capture=i);const a=t.$el.querySelector(`img[src="${i.image_url}"]`).parentElement;return n?a.scrollIntoView({block:"nearest",inline:"nearest",behavior:"auto"}):a.scrollIntoView({block:"nearest",inline:"nearest",behavior:"smooth"}),!0}if("Enter"===s.code){if(this.is_comment_send_just_did)return!1;if(!0===t.zoom_capture_modal)return t.zoom_capture_modal=!1,!0;const e=t.captures.find((t=>!0===t.focused));return void 0!==e&&(t.zoom_capture=e,t.zoom_capture_modal=!0,!0)}if("Space"===s.code){const e=t.captures.find((t=>!0===t.focused));return void 0!==e&&(t.clickCapture(e),!0)}}if(!1===n&&!s.ctrlKey&&!s.metaKey&&!s.shiftKey&&!s.altKey){if("ArrowUp"===s.code)return this.is_zapping=!0,await this.$router.push({path:`/tv/watch/${this.channelsStore.channel.previous.channel_id}`}),!0;if("ArrowDown"===s.code)return this.is_zapping=!0,await this.$router.push({path:`/tv/watch/${this.channelsStore.channel.next.channel_id}`}),!0}if(null!==this.player&&!s.shiftKey&&!s.altKey){if((s.ctrlKey||s.metaKey)&&"ArrowUp"===s.code)return this.player.volume(this.player.volume()+.05),!0;if((s.ctrlKey||s.metaKey)&&"ArrowDown"===s.code)return this.player.volume(this.player.volume()-.05),!0;if((s.ctrlKey||s.metaKey)&&"ArrowLeft"===s.code)return!1===this.player.video.paused&&this.player.video.pause(),this.player.video.currentTime=this.player.video.currentTime-.5,!0;if((s.ctrlKey||s.metaKey)&&"ArrowRight"===s.code)return!1===this.player.video.paused&&this.player.video.pause(),this.player.video.currentTime=this.player.video.currentTime+.5,!0}if(null!==this.player&&!s.ctrlKey&&!s.metaKey&&!s.altKey&&!0===s.shiftKey&&"Space"===s.code&&!1===n&&"Twitter"===this.tv_panel_active_tab&&"Capture"===t.twitter_active_tab)return this.player.toggle(),!0;if(null!==this.player&&!1===n&&!s.ctrlKey&&!s.metaKey&&!s.altKey){if("Space"===s.code)return this.player.toggle(),!0;if("KeyF"===s.code)return this.player.fullScreen.toggle(),!0;if("KeyW"===s.code)return this.player.sync(),!0;if("KeyE"===s.code)return document.pictureInPictureEnabled&&this.player.template.pipButton.click(),!0;if("KeyS"===s.code)return this.player.subtitle.toggle(),this.player.subtitle.container.classList.contains("dplayer-subtitle-hide")?this.player.notice(`${this.player.tran("Hide subtitle")}`):this.player.notice(`${this.player.tran("Show subtitle")}`),!0;if("KeyD"===s.code)return this.player.template.showDanmaku.click(),this.player.template.showDanmakuToggle.checked?this.player.notice(`${this.player.tran("Show comment")}`):this.player.notice(`${this.player.tran("Hide comment")}`),!0;if("KeyC"===s.code)return await this.capture_handler.captureAndSave(!1),!0;if("KeyV"===s.code)return await this.capture_handler.captureAndSave(!0),!0;if("KeyM"===s.code)return this.player.controller.show(),this.player.comment.show(),this.controlDisplayTimer(),window.setTimeout((()=>this.player.template.commentInput.focus()),100),!0}}return!1})();!0===o&&s.preventDefault()},document.addEventListener("keydown",this.shortcut_key_handler)},initCaptureHandler(){this.capture_handler=new Ai(this.player,((t,e)=>{this.$refs.Twitter.addCaptureList(t,e)}));const t=this.$el.querySelector(".dplayer-icon.dplayer-capture-icon");t.addEventListener("click",(async()=>{await this.capture_handler.captureAndSave(!1)}));const e=this.$el.querySelector(".dplayer-icon.dplayer-comment-capture-icon");e.addEventListener("click",(async()=>{await this.capture_handler.captureAndSave(!0)}))},async destroy(t=!1,e=!1){this.$refs.Comment.destroy();for(const i of this.interval_ids)window.clearInterval(i);if(window.clearTimeout(this.control_interval_id),this.interval_ids=[],this.is_loading=!0,this.is_background_display=!1,null!==this.player&&(this.player_can_be_destroyed=!0),null!==this.eventsource&&(this.eventsource.close(),this.eventsource=null),!1===e){const t=this.player.user.get("volume");for(let e=0;e<20;e++)await ht.sleep(.01),this.player.video.volume=t*(1-(e+1)/20)}if(!0===t&&null!==this.player){try{this.player.destroy()}catch(s){void 0!==this.player.plugins.mpegts&&this.player.plugins.mpegts.destroy()}this.player_can_be_destroyed=!1,this.player=null}}}}),Ki=Ni,Li=(0,h.Z)(Ki,Gs,Ws,!1,null,"7aded832",null),Ei=Li.exports;o["default"].use(z.ZP);const Hi=new z.ZP({mode:"history",base:"/",routes:[{path:"/",redirect:"/tv/"},{path:"/tv/",name:"TV Home",component:Vs},{path:"/tv/watch/:channel_id",name:"TV Watch",component:Ei},{path:"/settings/",name:"Settings Index",component:es,beforeEnter:(t,e,s)=>{ht.isSmartphoneVertical()||ht.isSmartphoneHorizontal()||ht.isTabletVertical()?s():s({path:"/settings/general/"})}},{path:"/settings/general",name:"Settings General",component:qe},{path:"/settings/caption",name:"Settings Caption",component:De},{path:"/settings/capture",name:"Settings Capture",component:He},{path:"/settings/account",name:"Settings Account",component:Te},{path:"/settings/jikkyo",name:"Settings Jikkyo",component:ps},{path:"/settings/twitter",name:"Settings Twitter",component:Zs},{path:"/settings/server",name:"Settings Server",component:bs},{path:"/login/",name:"Login",component:qt},{path:"/register/",name:"Register",component:oe},{path:"*",name:"NotFound",component:ee}],scrollBehavior(t,e,s){return s||{x:0,y:0}}});var Mi=Hi,Ui=s(5205);(0,Ui.z)("/service-worker.js",{ready(){console.log("App is being served from cache by a service worker.\nFor more details, visit https://goo.gl/AFskqB")},registered(){console.log("Service worker has been registered.")},cached(){console.log("Content has been cached for offline use.")},updatefound(){console.log("New content is downloading.")},updated(t){console.log("New content is available; please refresh."),K.show({message:"クライアントが新しいバージョンに更新されました。5秒後にリロードします。",timeout:1e4}),t.waiting.postMessage({type:"SKIP_WAITING"}),t.waiting.addEventListener("statechange",(async t=>{"activated"===t.target.state&&(await ht.sleep(4),location.reload(!0))}))},offline(){console.log("No internet connection found. App is running in offline mode.")},error(t){console.error("Error during service worker registration:",t)}}),(0,n.OK)(),o["default"].config.productionTip=!1,o["default"].config.devtools=!0,o["default"].use(a.og);const Vi=(0,a.WB)();o["default"].use(l.ZP),o["default"].use(_(),{top:!1,bottom:!0,color:"#433532",dark:!0,elevation:8,timeout:2500,autoRemove:!0,closeButtonContent:"閉じる",vuetifyInstance:Z});const Ri=ht.isTouchDevice()?[]:["hover","focus","touch"];r.ZP.options.themes.tooltip.showTriggers=Ri,r.ZP.options.themes.tooltip.hideTriggers=Ri,r.ZP.options.themes.tooltip.delay.show=0,r.ZP.options.offset=[0,7],o["default"].use(r.ZP),o["default"].component("Icon",i.JO),o["default"].component("v-tab-item-fix",w),o["default"].component("v-tabs-fix",x),o["default"].component("v-tabs-items-fix",O),window.KonomiTVVueInstance=new o["default"]({pinia:Vi,router:Mi,vuetify:Z,render:t=>t(v)}).$mount("#app");let Fi=!1;const Gi=st();Gi.$subscribe((async()=>{!0!==Fi&&(console.log("Client Settings Changed:",Gi.settings),Q(Gi.settings),await Gi.syncClientSettingsToServer())}),{detached:!0}),window.setInterval((async()=>{null!==ht.getAccessToken()&&!0===Gi.settings.sync_settings&&(Fi=!0,await Gi.syncClientSettingsFromServer(),Fi=!1,Q(Gi.settings))}),3e3)}},e={};function s(i){var a=e[i];if(void 0!==a)return a.exports;var n=e[i]={exports:{}};return t[i].call(n.exports,n,n.exports,s),n.exports}s.m=t,function(){var t=[];s.O=function(e,i,a,n){if(!i){var r=1/0;for(_=0;_=n)&&Object.keys(s.O).every((function(t){return s.O[t](i[l])}))?i.splice(l--,1):(o=!1,n0&&t[_-1][2]>n;_--)t[_]=t[_-1];t[_]=[i,a,n]}}(),function(){s.n=function(t){var e=t&&t.__esModule?function(){return t["default"]}:function(){return t};return s.d(e,{a:e}),e}}(),function(){s.d=function(t,e){for(var i in e)s.o(e,i)&&!s.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})}}(),function(){s.g=function(){if("object"===typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"===typeof window)return window}}()}(),function(){s.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)}}(),function(){s.r=function(t){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}}(),function(){var t={143:0};s.O.j=function(e){return 0===t[e]};var e=function(e,i){var a,n,r=i[0],o=i[1],l=i[2],c=0;if(r.some((function(e){return 0!==t[e]}))){for(a in o)s.o(o,a)&&(s.m[a]=o[a]);if(l)var _=l(s)}for(e&&e(i);c {\n\n // VueComponent の key が一致する this.$slots.default 内の VNode を探す\n const index_a = this.$slots.default.findIndex((element) => {\n return a.$vnode.key === element.key;\n });\n const index_b = this.$slots.default.findIndex((element) => {\n return b.$vnode.key === element.key;\n });\n\n // index 順で並び替え\n return index_a - index_b;\n });\n\n item.$on('change', () => (this as any).onClick(item));\n if ((this as any).mandatory && !(this as any).selectedValues.length) {\n (this as any).updateMandatory();\n }\n\n // 追加された要素のソート後のインデックスを取得して更新する\n (this as any).updateItem(item, this.items.indexOf(item));\n\n // ソート後の現在アクティブなタブのインデックスを取得し直し、設定する\n // 配列の末尾以外に追加された場合はインデックスが1つずつずれてしまうため、インデックスを設定し直す必要がある\n if (activeItem !== undefined) {\n (this as any).updateInternalValue(this.items.indexOf(activeItem));\n }\n },\n\n unregister(item: GroupableInstance) {\n\n // 現在アクティブなタブの VueComponent を取得\n const activeItem = this.items[(this as any).internalIndex];\n\n // 継承元の unregister() の処理を呼び出す(いわゆる super() )\n // ref: https://github.com/vuejs/vue/issues/2977\n (this.constructor as any).super.options.methods.unregister.call(this, item);\n\n // 配列の末尾以外から削除された場合はインデックスが1つずつずれてしまうため、インデックスを設定し直す必要がある\n if (activeItem !== undefined) {\n (this as any).updateInternalValue(this.items.indexOf(activeItem));\n }\n }\n }\n});\n","\nimport { VueConstructor, VNode } from 'vue';\nimport VTabs from 'vuetify/lib/components/VTabs/VTabs';\nimport { convertToUnit } from 'vuetify/lib/util/helpers';\n\nimport VTabsBar from '@/components/Vuetify/VTabsBar';\n\nexport default (VTabs as VueConstructor).extend({\n methods: {\n\n // VTabsBar は VTabs から暗黙的に生成されるコンポーネントのため、直接上書きすることができない\n // そこで VTabs 自体も上書きし、VTabs で $createElement() される時の VTabsBar を自前でオーバーライドしたものに差し替える\n // ビルド済みのファイルには型定義が入っていないので any を多用せざるを得ない…\n genBar(items: VNode[], slider: VNode | null) {\n const data = {\n style: {\n height: convertToUnit((this as any).height),\n },\n props: {\n activeClass: (this as any).activeClass,\n centerActive: (this as any).centerActive,\n dark: (this as any).dark,\n light: (this as any).light,\n mandatory: !(this as any).optional,\n mobileBreakpoint: (this as any).mobileBreakpoint,\n nextIcon: (this as any).nextIcon,\n prevIcon: (this as any).prevIcon,\n showArrows: (this as any).showArrows,\n value: (this as any).internalValue,\n },\n on: {\n 'call:slider': (this as any).callSlider,\n change: (val: any) => {\n (this as any).internalValue = val;\n },\n },\n ref: 'items',\n };\n\n (this as any).setTextColor((this as any).computedColor, data);\n (this as any).setBackgroundColor((this as any).backgroundColor, data);\n\n // ここでオーバーライドした VTabsBar を使うのが最重要\n // これをやるためだけにわざわざ VTabs に関してもオーバーライドする羽目になってる…\n return (this as any).$createElement(VTabsBar, data, [\n (this as any).genSlider(slider),\n items,\n ]);\n }\n }\n});\n","\nimport { VueConstructor } from 'vue';\nimport { GroupableInstance } from 'vuetify/lib/components/VItemGroup/VItemGroup';\nimport VTabsItems from 'vuetify/lib/components/VTabs/VTabsItems';\n\n// VTabsItems は VItemGroup と VWindow を extend() して実装されている\nexport default (VTabsItems as VueConstructor).extend({\n data() {\n return {\n // 一応型定義をしておく\n items: [] as GroupableInstance[],\n };\n },\n methods: {\n\n // タブのデータ配列の先頭に新しい要素が追加されるとそのタブのアニメーションの向きが逆になるバグがあるので、VItemGroup 側の挙動をオーバーライドする\n // DOM 上も VNode 上も正しい順序で並んでいるが、this.items に関しては追加された順になっていてしまっていて齟齬が発生するのが原因\n // ref: https://github.com/vuetifyjs/vuetify/issues/13862\n register(item: GroupableInstance) {\n\n // 現在アクティブなタブの VueComponent を取得\n const activeItem = this.items[(this as any).internalIndex];\n\n // 要素を items に追加\n this.items.push(item);\n\n // this.$slots.default に VNode が、items には単に VueComponent が入っているので、事前に VNode の順番に合わせて並べ替える\n // こうすることで、追加された順ではなく元のデータ配列通りの順番になる\n this.items.sort((a, b) => {\n\n // VueComponent の key が一致する this.$slots.default 内の VNode を探す\n const index_a = this.$slots.default.findIndex((element) => {\n return a.$vnode.key === element.key;\n });\n const index_b = this.$slots.default.findIndex((element) => {\n return b.$vnode.key === element.key;\n });\n\n // index 順で並び替え\n return index_a - index_b;\n });\n\n item.$on('change', () => (this as any).onClick(item));\n if ((this as any).mandatory && !(this as any).selectedValues.length) {\n (this as any).updateMandatory();\n }\n\n // 追加された要素のソート後のインデックスを取得して更新する\n (this as any).updateItem(item, this.items.indexOf(item));\n\n // ソート後の現在アクティブなタブのインデックスを取得し直し、設定する\n // 配列の末尾以外に追加された場合はインデックスが1つずつずれてしまうため、インデックスを設定し直す必要がある\n if (activeItem !== undefined) {\n // 値が異なるときだけ更新する\n // こうしないと、Safari で変なアニメーションがついてしまう\n if (this.items.indexOf(activeItem) !== (this as any).internalValue) {\n (this as any).updateInternalValue(this.items.indexOf(activeItem));\n }\n }\n },\n\n unregister(item: GroupableInstance) {\n\n // 現在アクティブなタブの VueComponent を取得\n const activeItem = this.items[(this as any).internalIndex];\n\n // 継承元の unregister() の処理を呼び出す(いわゆる super() )\n // ref: https://github.com/vuejs/vue/issues/2977\n (this.constructor as any).super.options.methods.unregister.call(this, item);\n\n // 配列の末尾以外から削除された場合はインデックスが1つずつずれてしまうため、インデックスを設定し直す必要がある\n if (activeItem !== undefined) {\n (this as any).updateInternalValue(this.items.indexOf(activeItem));\n }\n },\n\n // 最初のタブから最後のタブに遷移するとアニメーションの向きが逆になるバグがあるので、VWindow 側の挙動をオーバーライドする\n // 本来は VCarousel 用の動作だが、VTabsItems も VWindow を継承しているので、それが適用されてしまっているらしい\n // ref: https://github.com/yuwu9145/vuetify/blob/master/packages/vuetify/src/components/VWindow/VWindow.ts#L239-L252\n updateReverse(val: number, oldVal: number) {\n\n const itemsLength = this.items.length;\n const lastIndex = itemsLength - 1;\n\n if (itemsLength <= 2) return val < oldVal;\n\n // continuous が false の時、常に val < oldVal の結果を返す\n if (!(this as any).continuous) return val < oldVal;\n\n if (val === lastIndex && oldVal === 0) {\n return true;\n } else if (val === 0 && oldVal === lastIndex) {\n return false;\n } else {\n return val < oldVal;\n }\n }\n }\n});\n","\nimport Vue from 'vue';\nimport { VSnackbar, VBtn, VIcon } from 'vuetify/lib';\nimport Vuetify from 'vuetify/lib/framework';\n\n\nVue.use(Vuetify);\n\n// vuetify-message-snackbar を使うのに必要\nVue.component('v-snackbar', VSnackbar);\nVue.component('v-btn', VBtn);\nVue.component('v-icon', VIcon);\n\nexport default new Vuetify({\n theme: {\n dark: true,\n themes: {\n dark: {\n primary: '#E64F97',\n secondary: '#E33157',\n twitter: {\n base: '#4F82E6',\n lighten1: '#799FEC',\n lighten2: '#41A5F1',\n },\n gray: '#66514C',\n black: '#110A09',\n background: {\n base: '#1E1310',\n lighten1: '#2F221F',\n lighten2: '#433532',\n lighten3: '#4c3c38',\n },\n text: {\n base: '#FFEAEA',\n darken1: '#D9C7C7',\n darken2: '#8E7F7E',\n darken3: '#786968',\n }\n }\n },\n options: {\n customProperties: true,\n },\n },\n});\n","\n/**\n * 共通ユーティリティ\n */\nexport default class Utils {\n\n // バージョン情報\n // ビルド時の環境変数 (vue.config.js に記載) から取得\n static readonly version: string = process.env.VUE_APP_VERSION;\n\n // バックエンドの API のベース URL\n static readonly api_base_url = (() => {\n if (process.env.NODE_ENV === 'development') {\n // デバッグ時はポートを 7000 に強制する\n return `${window.location.protocol}//${window.location.hostname}:7000/api`;\n } else {\n // ビルド後は同じポートを使う\n return `${window.location.protocol}//${window.location.host}/api`;\n }\n })();\n\n\n /**\n * アクセストークンを LocalStorage から取得する\n * @returns JWT アクセストークン(ログインしていない場合は null が返る)\n */\n static getAccessToken(): string | null {\n\n // LocalStorage の取得結果をそのまま返す\n // LocalStorage.getItem() はキーが存在しなければ(=ログインしていなければ)null を返す\n return localStorage.getItem('KonomiTV-AccessToken');\n }\n\n\n /**\n * アクセストークンを LocalStorage に保存する\n * @param access_token 発行された JWT アクセストークン\n */\n static saveAccessToken(access_token: string): void {\n\n // そのまま LocalStorage に保存\n localStorage.setItem('KonomiTV-AccessToken', access_token);\n }\n\n\n /**\n * アクセストークンを LocalStorage から削除する\n * アクセストークンを削除することで、ログアウト相当になる\n */\n static deleteAccessToken(): void {\n\n // LocalStorage に KonomiTV-AccessToken キーが存在しない\n if (localStorage.getItem('KonomiTV-AccessToken') === null) return;\n\n // KonomiTV-AccessToken キーを削除\n localStorage.removeItem('KonomiTV-AccessToken');\n }\n\n\n /**\n * ブラウザが実行されている OS に応じて、\"Alt\" または \"Option\" を返す\n * キーボードショートカットのコンビネーションキーの説明を OS によって分けるために使う\n * @returns ブラウザが実行されている OS が Mac なら Option を、それ以外なら Alt を返す\n */\n static AltOrOption(): 'Alt' | 'Option' {\n // iPhone・iPad で純正キーボードを接続した場合も一応想定して、iPhone・iPad も含める(動くかは未検証)\n return /iPhone|iPad|Macintosh/i.test(navigator.userAgent) ? 'Option' : 'Alt';\n }\n\n\n /**\n * ブラウザが実行されている OS に応じて、\"Ctrl\" または \"Cmd\" を返す\n * キーボードショートカットのコンビネーションキーの説明を OS によって分けるために使う\n * @returns ブラウザが実行されている OS が Mac なら Cmd を、それ以外なら Ctrl を返す\n */\n static CtrlOrCmd(): 'Ctrl' | 'Cmd' {\n // iPhone・iPad で純正キーボードを接続した場合も一応想定して、iPhone・iPad も含める(動くかは未検証)\n return /iPhone|iPad|Macintosh/i.test(navigator.userAgent) ? 'Cmd' : 'Ctrl';\n }\n\n\n /**\n * Blob に格納されているデータをブラウザにダウンロードさせる\n * @param blob Blob オブジェクト\n * @param filename 保存するファイル名\n */\n static downloadBlobData(blob: Blob, filename: string): void {\n\n // Blob URL を発行\n const blob_url = URL.createObjectURL(blob);\n\n // 画像をダウンロード\n const link = document.createElement('a');\n link.download = filename;\n link.href = blob_url;\n link.click();\n\n // Blob URL を破棄\n URL.revokeObjectURL(blob_url);\n }\n\n\n /**\n * innerHTML しても問題ないように HTML の特殊文字をエスケープする\n * PHP の htmlspecialchars() と似たようなもの\n * @param content HTML エスケープされてないテキスト\n * @returns HTML エスケープされたテキスト\n */\n static escapeHTML(content: string): string {\n\n // HTML エスケープが必要な文字\n // ref: https://www.php.net/manual/ja/function.htmlspecialchars.php\n const html_escape_table = {\n '&': '&',\n '\"': '"',\n '\\'': ''',\n '<': '<',\n '>': '>',\n };\n\n // ref: https://qiita.com/noriaki/items/4bfef8d7cf85dc1035b3\n return content.replace(/[&\"'<>]/g, (match) => {\n return html_escape_table[match];\n });\n }\n\n\n /**\n * OAuth 連携時のポップアップを画面中央に表示するための windowFeatures 文字列を取得する\n * ref: https://qiita.com/catatsuy/items/babce8726ea78f5d25b1\n * @returns window.open() で使う windowFeatures 文字列\n */\n static getWindowFeatures(): string {\n\n // ポップアップウインドウのサイズ\n const popupSizeWidth = 650;\n const popupSizeHeight = window.screen.height >= 800 ? 800 : window.screen.height - 100;\n\n // ポップアップウインドウの位置\n const posTop = (window.screen.height - popupSizeHeight) / 2;\n const posLeft = (window.screen.width - popupSizeWidth) / 2;\n\n return `toolbar=0,status=0,top=${posTop},left=${posLeft},width=${popupSizeWidth},height=${popupSizeHeight},modal=yes,alwaysRaised=yes`;\n }\n\n\n /**\n * 現在フォーカスを持っている要素に指定された CSS クラスが付与されているか\n * @param class_name 存在を確認する CSS クラス名\n * @returns document.activeElement が class_name で指定したクラスを持っているかどうか\n */\n static hasActiveElementClass(class_name: string): boolean {\n if (document.activeElement === null) return false;\n return document.activeElement.classList.contains(class_name);\n }\n\n\n /**\n * ブラウザが Firefox かどうか\n * @returns ブラウザが Firefox なら true を返す\n */\n static isFirefox(): boolean {\n return /Firefox/i.test(navigator.userAgent);\n }\n\n\n /**\n * モバイルデバイス(スマホ・タブレット)かどうか\n * @returns モバイルデバイス (スマホ・タブレット) なら true を返す\n */\n static isMobileDevice(): boolean {\n // Macintosh が入っているのは、iPadOS は既定でデスクトップ表示モードが有効になっていて、UA だけでは Mac と判別できないため\n // Mac にタッチパネル付きの機種は存在しないので、'ontouchend' in document で判定できる\n return /iPhone|iPad|iPod|Macintosh|Android|Mobile/i.test(navigator.userAgent) && 'ontouchend' in document;\n }\n\n\n /**\n * 表示画面がスマホ横画面かどうか\n * @returns スマホ横画面なら true を返す\n */\n static isSmartphoneHorizontal(): boolean {\n return window.matchMedia('(max-width: 1000px) and (max-height: 450px)').matches;\n }\n\n\n /**\n * 表示画面がスマホ縦画面かどうか\n * @returns スマホ縦画面なら true を返す\n */\n static isSmartphoneVertical(): boolean {\n return window.matchMedia('(max-width: 600px) and (min-height: 450.01px)').matches;\n }\n\n\n /**\n * 表示画面がタブレット横画面かどうか\n * @returns タブレット横画面なら true を返す\n */\n static isTabletHorizontal(): boolean {\n return window.matchMedia('(max-width: 1264px) and (max-height: 850px)').matches;\n }\n\n\n /**\n * 表示画面がタブレット縦画面かどうか\n * @returns タブレット縦画面なら true を返す\n */\n static isTabletVertical(): boolean {\n return window.matchMedia('(max-width: 850px) and (min-height: 850.01px)').matches;\n }\n\n\n /**\n * 表示端末がタッチデバイスかどうか\n * @returns タッチデバイスなら true を返す\n */\n static isTouchDevice(): boolean {\n return window.matchMedia('(hover: none)').matches;\n }\n\n\n /**\n * 任意の桁で切り捨てする\n * ref: https://qiita.com/nagito25/items/0293bc317067d9e6c560#comment-87f0855f388953843037\n * @param value 切り捨てする数値\n * @param base どの桁で切り捨てするか (-1 → 10の位 / 3 → 小数第3位)\n * @return 切り捨てした値\n */\n static mathFloor(value: number, base: number = 0): number {\n return Math.floor(value * (10**base)) / (10**base);\n }\n\n\n /**\n * async/await でスリープ的なもの\n * @param seconds 待機する秒数 (ミリ秒単位ではないので注意)\n * @returns Promise を返すので、await sleep(1); のように使う\n */\n static async sleep(seconds: number): Promise {\n return await new Promise(resolve => setTimeout(resolve, seconds * 1000));\n }\n\n\n /**\n * 現在時刻の UNIX タイムスタンプ (秒単位) を取得する (デバッグ用)\n * @returns 現在時刻の UNIX タイムスタンプ (秒単位)\n */\n static time(): number {\n return Date.now() / 1000;\n }\n\n\n /**\n * 指定された値の型の名前を取得する\n * ref: https://qiita.com/amamamaou/items/ef0b797156b324bb4ef3\n * @returns 指定された値の型の名前\n */\n static typeof(value: any): string {\n return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();\n }\n\n\n /**\n * 文字列中に含まれる URL をリンクの HTML に置き換える\n * @param text 置換対象の文字列\n * @returns URL をリンクに置換した文字列\n */\n static URLtoLink(text: string): string {\n\n // HTML の特殊文字で表示がバグらないように、事前に HTML エスケープしておく\n text = Utils.escapeHTML(text);\n\n // ref: https://www.softel.co.jp/blogs/tech/archives/6099\n const pattern = /(https?:\\/\\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/ig;\n return text.replace(pattern, '$1');\n }\n}\n","\nimport { ChannelType } from '@/services/Channels';\n\n\n/**\n * チャンネル周りのユーティリティ\n */\nexport class ChannelUtils {\n\n /**\n * チャンネル ID からチャンネルタイプを取得する\n * @param channel_id チャンネル ID\n * @returns チャンネルタイプ\n */\n static getChannelType(channel_id: string): ChannelType {\n try {\n const result = channel_id.match('(?[a-z]+)[0-9]+').groups.channel_type.toUpperCase();\n return result as ChannelType;\n } catch (e) {\n // 何かしらエラーが発生したということはチャンネル ID が不正\n // とりあえずここではエラーにならないよう GR を返す エラー処理はその後の段階で行われる\n return 'GR';\n }\n }\n\n\n /**\n * チャンネルの実況勢いから「多」「激多」「祭」を取得する\n * ref: https://ja.wikipedia.org/wiki/%E3%83%8B%E3%82%B3%E3%83%8B%E3%82%B3%E5%AE%9F%E6%B3%81\n * @param channel_force チャンネルの実況勢い\n * @returns normal(普通)or many(多)or so-many(激多)or festival(祭)\n */\n static getChannelForceType(channel_force: number | null): 'normal' | 'many' | 'so-many' | 'festival' {\n\n // 実況勢いが null(=対応する実況チャンネルがない)\n if (channel_force === null) return 'normal';\n\n // 実況勢いが 500 コメント以上(祭)\n if (channel_force >= 500) return 'festival';\n // 実況勢いが 200 コメント以上(激多)\n if (channel_force >= 200) return 'so-many';\n // 実況勢いが 100 コメント以上(多)\n if (channel_force >= 100) return 'many';\n\n // それ以外\n return 'normal';\n }\n}\n","\nimport { MessageOption, MessageType } from 'vuetify-message-snackbar/src/message';\n\n\ninterface MessageReturnValue {\n close(): void;\n again(): void;\n}\n\n// Vue コンポーネント以外からも this.$message を使えるようにするための (強引な) ラッパー\nexport default {\n success(message: MessageType | MessageOption): MessageReturnValue {\n // @ts-ignore\n return window.KonomiTVVueInstance?.$message.success(message);\n },\n info(message: MessageType | MessageOption): MessageReturnValue {\n // @ts-ignore\n return window.KonomiTVVueInstance?.$message.info(message);\n },\n warning(message: MessageType | MessageOption): MessageReturnValue {\n // @ts-ignore\n return window.KonomiTVVueInstance?.$message.warning(message);\n },\n error(message: MessageType | MessageOption): MessageReturnValue {\n // @ts-ignore\n return window.KonomiTVVueInstance?.$message.error(message);\n },\n show(message: MessageType | MessageOption): MessageReturnValue {\n // @ts-ignore\n return window.KonomiTVVueInstance?.$message.show(message);\n }\n};\n","\nimport axios from 'axios';\n\nimport Utils from '@/utils';\n\n\n// Axios のインスタンスを作成\nconst axios_instance = axios.create();\n\n// HTTP リクエスト前に割り込んで行われる処理\naxios_instance.interceptors.request.use((config) => {\n\n // API のベース URL を設定 (config.baseURL が指定されていない場合のみ)\n if (config.baseURL === undefined) {\n config.baseURL = Utils.api_base_url;\n }\n\n // 外部サイトへの HTTP リクエストでは実行しない\n if (config.url?.startsWith('http') === false) {\n\n // アクセストークンが取得できたら(=ログインされていれば)\n // 取得したアクセストークンを Authorization ヘッダーに Bearer トークンとしてセット\n // これを忘れると(当然ながら)ログインしていない扱いになる\n const access_token = Utils.getAccessToken();\n if (access_token !== null) {\n config.headers['Authorization'] = `Bearer ${access_token}`;\n }\n\n // KonomiTV クライアントのバージョンを設定\n // 今のところ使わないが、将来的にクライアントとサーバーを分離することを見据えて念のため\n config.headers['X-KonomiTV-Version'] = Utils.version;\n }\n\n // タイムアウト時間を30秒に設定\n config.timeout = 30 * 1000;\n\n return config;\n});\n\nexport default axios_instance;\n","\nimport Message from '@/message';\nimport APIClient from '@/services/APIClient';\n\n\n/** ユーザーアカウントの情報を表すインターフェイス */\nexport interface IUser {\n id: number;\n name: string;\n is_admin: boolean;\n niconico_user_id: number | null;\n niconico_user_name: string | null;\n niconico_user_premium: boolean | null;\n twitter_accounts: ITwitterAccount[];\n created_at: string;\n updated_at: string;\n}\n\n/** ユーザーアカウントに紐づく Twitter アカウントの情報を表すインターフェイス */\nexport interface ITwitterAccount {\n id: number;\n name: string;\n screen_name: string;\n icon_url: string;\n is_oauth_session: boolean;\n created_at: string;\n updated_at: string;\n}\n\n/** ユーザーアカウントのアクセストークンを表すインターフェイス */\nexport interface IUserAccessToken {\n access_token: string;\n token_type: string;\n}\n\nexport interface IUserCreateRequest {\n username: string;\n password: string;\n}\nexport interface IUserUpdateRequest {\n username?: string;\n password?: string;\n}\nexport interface IUserUpdateRequestForAdmin {\n username?: string;\n password?: string;\n is_admin?: boolean;\n}\n\n\nclass Users {\n\n /**\n * ユーザーアカウントを作成する\n * @param user_create_request ユーザー名とパスワード\n * @returns 作成したユーザーアカウントの情報 or アカウント作成に失敗した場合は null\n */\n static async createUser(user_create_request: IUserCreateRequest): Promise {\n\n // API リクエストを実行\n const response = await APIClient.post('/users', user_create_request);\n\n // エラー処理\n if ('is_error' in response) {\n switch (response.error.message) {\n case 'Specified username is duplicated': {\n Message.error('ユーザー名が重複しています。');\n break;\n }\n case 'Specified username is not accepted due to system limitations': {\n Message.error('ユーザー名に token と me は使えません。');\n break;\n }\n default: {\n APIClient.showGenericError(response, 'アカウントを作成できませんでした。');\n break;\n }\n }\n return null;\n }\n\n return response.data;\n }\n\n\n /**\n * ユーザーアカウントのアクセストークンを発行する\n * @param username ユーザー名\n * @param password パスワード\n * @returns 発行したアクセストークン or ログインに失敗した場合は null\n */\n static async createUserAccessToken(username: string, password: string): Promise {\n\n // API リクエストを実行\n const response = await APIClient.post('/users/token', new URLSearchParams({username, password}));\n\n // エラー処理\n if ('is_error' in response) {\n switch (response.error.message) {\n case 'Incorrect username': {\n Message.error('ログインできませんでした。そのユーザー名のアカウントは存在しません。');\n break;\n }\n case 'Incorrect password': {\n Message.error('ログインできませんでした。パスワードを間違えていませんか?');\n break;\n }\n default: {\n APIClient.showGenericError(response, 'ログインできませんでした。');\n break;\n }\n }\n return null;\n }\n\n return response.data;\n }\n\n\n /**\n * 現在ログイン中のユーザーアカウントの情報を取得する\n * @returns ログイン中のユーザーアカウントの情報 or ログインしていない場合は null\n */\n static async fetchUser(): Promise {\n\n // API リクエストを実行\n const response = await APIClient.get('/users/me');\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'アカウント情報を取得できませんでした。');\n return null;\n }\n\n return response.data;\n }\n\n\n /**\n * 現在ログイン中のユーザーアカウントのアイコンを取得する\n * @returns ログイン中のユーザーアカウントのアイコンの Blob URL or ログインしていない場合は null\n */\n static async fetchUserIcon(): Promise {\n\n // API リクエストを実行\n const response = await APIClient.get('/users/me/icon', {responseType: 'blob'});\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'アイコン画像を取得できませんでした。');\n return null;\n }\n\n return URL.createObjectURL(response.data);\n }\n\n\n /**\n * 現在ログイン中のユーザーアカウントの情報を更新する\n * @param user_update_request ユーザー名 or パスワード\n */\n static async updateUser(user_update_request: IUserUpdateRequest): Promise {\n\n // API リクエストを実行\n const response = await APIClient.put('/users/me', user_update_request);\n\n // エラー処理\n if ('is_error' in response) {\n switch (response.error.message) {\n case 'Specified username is duplicated': {\n Message.error('ユーザー名が重複しています。');\n break;\n }\n case 'Specified username is not accepted due to system limitations': {\n Message.error('ユーザー名に token と me は使えません。');\n break;\n }\n default: {\n APIClient.showGenericError(response, 'アカウント情報を更新できませんでした。');\n break;\n }\n }\n return;\n }\n }\n\n\n /**\n * 現在ログイン中のユーザーアカウントのアイコン画像を更新する\n * @param icon アイコンの File オブジェクト\n */\n static async updateUserIcon(icon: File): Promise {\n\n // アイコン画像の File オブジェクト (= Blob) を FormData に入れる\n // multipart/form-data で送るために必要\n // ref: https://r17n.page/2020/02/04/nodejs-axios-file-upload-api/\n const form_data = new FormData();\n form_data.append('image', icon);\n\n // API リクエストを実行\n const response = await APIClient.put('/users/me/icon', form_data, {headers: {'Content-Type': 'multipart/form-data'}});\n\n // エラー処理\n if ('is_error' in response) {\n switch (response.error.message) {\n case 'Please upload JPEG or PNG image': {\n Message.error('JPEG または PNG 画像をアップロードしてください。');\n break;\n }\n default: {\n APIClient.showGenericError(response, 'アイコン画像を更新できませんでした。');\n break;\n }\n }\n return;\n }\n }\n\n\n /**\n * 現在ログイン中のユーザーアカウントを削除する\n */\n static async deleteUser(): Promise {\n\n // API リクエストを実行\n const response = await APIClient.delete('/users/me');\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'アカウントを削除できませんでした。');\n return;\n }\n }\n}\n\nexport default Users;\n","\nimport { defineStore } from 'pinia';\n\nimport Message from '@/message';\nimport Users, { IUser, IUserUpdateRequest } from '@/services/Users';\nimport useSettingsStore from '@/store/SettingsStore';\nimport Utils from '@/utils';\n\n\n/**\n * 現在ログイン中のユーザーアカウントの情報を共有するストア\n */\nconst useUserStore = defineStore('user', {\n state: () => ({\n\n // 現在ログイン中かどうか\n is_logged_in: false as boolean,\n\n // ログイン済みのユーザーの情報\n user: null as IUser | null,\n\n // ログイン済みのユーザーのアイコン画像の Blob URL\n user_icon_url: null as string | null,\n }),\n getters: {\n\n /**\n * ログイン済みのユーザーのニコニコアカウントのユーザーアイコンの URL (ニコニコアカウントと連携されている場合のみ)\n */\n user_niconico_icon_url(): string | null {\n if (this.user === null || this.user.niconico_user_id === null) {\n return null;\n }\n const user_id_slice = this.user.niconico_user_id.toString().slice(0, 4);\n return `https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/${user_id_slice}/${this.user.niconico_user_id}.jpg`;\n }\n },\n actions: {\n\n /**\n * アカウントを作成する\n * @param username ユーザー名\n * @param password パスワード\n * @returns アカウント作成に成功した場合は true\n */\n async register(username: string, password: string): Promise {\n\n // アカウントを作成\n const result = await Users.createUser({username, password});\n if (result === null) {\n console.log('Register failed.');\n return false; // アカウント作成失敗 (エラーハンドリングは services 層で行われる)\n }\n\n // 作成したアカウントでログイン\n await this.login(username, password, true);\n console.log('Register successful.');\n Message.success('アカウントを作成しました。');\n return true;\n },\n\n /**\n * ログイン処理を行う\n * @param username ユーザー名\n * @param password パスワード\n * @param silent ログインしたことをメッセージで通知しない場合は true\n * @returns ログインに成功した場合は true\n */\n async login(username: string, password: string, silent: boolean = false): Promise {\n\n // アクセストークンを発行\n const access_token = await Users.createUserAccessToken(username, password);\n if (access_token === null) {\n console.log('Login failed.');\n this.logout(true);\n return false; // ログイン失敗 (エラーハンドリングは services 層で行われる)\n }\n\n // 取得したアクセストークンを保存\n console.log('Login successful.');\n Utils.saveAccessToken(access_token.access_token);\n this.is_logged_in = true;\n\n // ユーザーアカウントの情報を取得\n await this.fetchUser(true);\n\n if (silent === false) {\n Message.success('ログインしました。');\n }\n\n return true;\n },\n\n /**\n * ログアウト処理を行う\n * @param silent ログアウトしたことをメッセージで通知しない場合は true\n */\n logout(silent: boolean = false): void {\n\n // 設定の同期を無効化\n const settings_store = useSettingsStore();\n settings_store.settings.sync_settings = false;\n\n // ブラウザからアクセストークンを削除\n // これをもってログアウトしたことになる(それ以降の Axios のリクエストにはアクセストークンが含まれなくなる)\n Utils.deleteAccessToken();\n\n // 未ログイン状態に設定\n this.is_logged_in = false;\n this.user = null;\n this.user_icon_url = '';\n\n if (silent === false) {\n Message.success('ログアウトしました。');\n }\n },\n\n /**\n * 現在ログイン中のユーザーアカウントの情報を取得する\n * すでに取得済みの情報がある場合は API リクエストを行わずにそれを返す\n * @param force 強制的に API リクエストを行う場合は true\n * @returns ログイン中のユーザーアカウントの情報 or ログインしていない場合は null\n */\n async fetchUser(force: boolean = false): Promise {\n\n // LocalStorage にアクセストークンが保存されていない場合 (= 非ログイン状態) は常に null を返す\n if (Utils.getAccessToken() === null) {\n return null;\n }\n\n // すでにログイン済みのユーザーアカウントの情報がある場合はそれを返す\n // force が true の場合は無視される\n if (this.user !== null && force === false) {\n return this.user;\n }\n\n // ユーザーアカウントの情報を取得する\n const user = await Users.fetchUser();\n if (user === null) {\n // この時点で無効などの理由でアクセストークンが削除されている場合、ログアウトする\n if (Utils.getAccessToken() === null) {\n this.logout(true);\n }\n return null;\n }\n this.is_logged_in = true;\n this.user = user;\n\n // ユーザーアカウントのアイコン画像の Blob URL を取得する\n const user_icon_url = await Users.fetchUserIcon();\n if (user_icon_url === null) {\n return null;\n }\n this.user_icon_url = user_icon_url;\n\n return this.user;\n },\n\n /**\n * 現在ログイン中のユーザーアカウントの情報を更新する\n * @param user_update_request ユーザー名 or パスワード\n */\n async updateUser(user_update_request: IUserUpdateRequest): Promise {\n\n // ユーザーアカウントの情報を更新する\n await Users.updateUser(user_update_request);\n\n // ユーザーアカウントの情報を再取得する\n await this.fetchUser(true);\n\n if (user_update_request.username !== undefined) {\n Message.show('ユーザー名を更新しました。');\n } else if (user_update_request.password !== undefined) {\n Message.show('パスワードを更新しました。');\n }\n },\n\n /**\n * 現在ログイン中のユーザーアカウントのアイコン画像を更新する\n * @param icon アイコンの File オブジェクト\n */\n async updateUserIcon(icon: File): Promise {\n\n // ユーザーアカウントのアイコン画像を更新する\n await Users.updateUserIcon(icon);\n\n // ユーザーアカウントの情報を再取得する\n await this.fetchUser(true);\n\n Message.show('アイコン画像を更新しました。');\n },\n\n /**\n * 現在ログイン中のユーザーアカウントを削除する\n */\n async deleteUser(): Promise {\n\n // ユーザーアカウントを削除する\n await Users.deleteUser();\n\n // ログアウトする\n this.logout(true);\n\n Message.show('アカウントを削除しました。');\n }\n }\n});\n\nexport default useUserStore;\n","\n/**\n * services/ 以下の各クラスは、KonomiTV サーバーへの API リクエストを抽象化し、\n * API レスポンスの受け取りと、エラーが発生した際のエラーハンドリング (エラーメッセージ表示) までを責務として負う\n */\n\nimport { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';\n\nimport Message from '@/message';\nimport axios from '@/plugins/axios';\nimport useUserStore from '@/store/UserStore';\n\n\n/** API のエラーレスポンスを表すインターフェイス */\nexport interface IError {\n detail: string;\n}\n\n/** API リクエスト成功時のレスポンスを表すインターフェイス */\nexport type SuccessResponse = {\n status: number;\n data: T;\n error: null;\n is_success: true;\n};\n\n/** API リクエスト失敗時のレスポンスを表すインターフェイス */\nexport type ErrorResponse = {\n status: number;\n data: null;\n error: T;\n is_error: true;\n};\n\n\n/**\n * services/ 以下の各クラスから呼び出される、Axios の薄いラッパー\n * エラーハンドリングを容易にするために、レスポンスを SuccessResponse と ErrorResponse に分けて返す\n * ref: https://zenn.dev/engineer_titan/articles/291c9fccb338e2\n */\nclass APIClient {\n\n /**\n * Axios で HTTP リクエストを送信し、レスポンスを受け取る\n * @param request AxiosRequestConfig\n * @returns 成功なら SuccessResponse 、失敗なら ErrorResponse を返す\n */\n static async request(request: AxiosRequestConfig): Promise | ErrorResponse> {\n\n // Axios で HTTP リクエストを送信し、レスポンスを受け取る\n const result: AxiosResponse | AxiosError = await axios.request(request).catch((error: AxiosError) => error);\n\n // エラーが発生した場合は ErrorResponse を返す\n if (result instanceof AxiosError) {\n console.error(result);\n\n // エラーレスポンスがあれば、エラー内容を取得して返す\n if (result.response) {\n return {\n status: result.response.status,\n data: null,\n error: new Error(result.response.data.detail),\n is_error: true,\n };\n\n // エラーレスポンスがない場合は、AxiosError をそのまま返す\n } else {\n return {\n status: NaN,\n data: null,\n error: result,\n is_error: true,\n };\n }\n\n // 正常にレスポンスが返ってきた場合は SuccessResponse を返す\n } else {\n return {\n status: result.status,\n data: result.data,\n error: null,\n is_success: true,\n };\n }\n }\n\n\n /**\n * GET リクエストを送信する\n * @param url リクエスト先の URL\n * @returns 成功なら SuccessResponse 、失敗なら ErrorResponse を返す\n */\n static async get(url: string, config?: AxiosRequestConfig): Promise | ErrorResponse> {\n const request: AxiosRequestConfig = {\n url: url,\n method: 'GET',\n ...config,\n };\n return await APIClient.request(request);\n }\n\n\n /**\n * POST リクエストを送信する\n * @param url リクエスト先の URL\n * @returns 成功なら SuccessResponse 、失敗なら ErrorResponse を返す\n */\n static async post(url: string, data?: D, config?: AxiosRequestConfig): Promise | ErrorResponse> {\n const request: AxiosRequestConfig = {\n url: url,\n method: 'POST',\n data: data,\n ...config,\n };\n return await APIClient.request(request);\n }\n\n\n /**\n * PUT リクエストを送信する\n * @param url リクエスト先の URL\n * @returns 成功なら SuccessResponse 、失敗なら ErrorResponse を返す\n */\n static async put(url: string, data?: D, config?: AxiosRequestConfig): Promise | ErrorResponse> {\n const request: AxiosRequestConfig = {\n url: url,\n method: 'PUT',\n data: data,\n ...config,\n };\n return await APIClient.request(request);\n }\n\n\n /**\n * DELETE リクエストを送信する\n * @param url リクエスト先の URL\n * @returns 成功なら SuccessResponse 、失敗なら ErrorResponse を返す\n */\n static async delete(url: string, config?: AxiosRequestConfig): Promise | ErrorResponse> {\n const request: AxiosRequestConfig = {\n url: url,\n method: 'DELETE',\n ...config,\n };\n return await APIClient.request(request);\n }\n\n\n /**\n * 一般的なエラーメッセージの共通処理\n * エラーメッセージを SnackBar で表示する\n * @param response API から返されたエラーレスポンス\n * @param template エラーメッセージのテンプレート(「アカウント情報を取得できませんでした。」など)\n */\n static showGenericError(response: ErrorResponse, template: string): void {\n const user_store = useUserStore();\n switch (response.error.message) {\n case 'Not authenticated': {\n user_store.logout(true);\n Message.error(`${template}\\nログインし直してください。`);\n return;\n }\n case 'Access token data is invalid': {\n user_store.logout(true);\n Message.error(`${template}\\nログインセッションが不正です。もう一度ログインし直してください。`);\n return;\n }\n case 'Access token is invalid': {\n user_store.logout(true);\n Message.error(`${template}\\nログインセッションの有効期限が切れています。もう一度ログインし直してください。`);\n return;\n }\n case 'User associated with access token does not exist': {\n user_store.logout(true);\n Message.error(`${template}\\nログインセッションに紐づくユーザーが存在しないか、削除されています。`);\n return;\n }\n default: {\n if (response.error.message) {\n if (Number.isNaN(response.status)) {\n Message.error(`${template}(${response.error.message})`);\n } else {\n Message.error(`${template}(HTTP Error ${response.status} / ${response.error.message})`);\n }\n } else {\n Message.error(`${template}(HTTP Error ${response.status})`);\n }\n return;\n }\n }\n }\n}\n\nexport default APIClient;\n","\nimport APIClient from '@/services/APIClient';\n\n\n/**\n * サーバーに保存されるクライアント設定を表すインターフェース\n * サーバー側の app.schemas.ClientSettings と\n * client/src/store/SettingsStore.ts 内の sync_settings_keys で定義されているものと同じ\n */\nexport interface IClientSettings {\n pinned_channel_ids: string[];\n // showed_panel_last_time: 同期無効\n // selected_twitter_account_id: 同期無効\n saved_twitter_hashtags: string[];\n // tv_streaming_quality: 同期無効\n // tv_data_saver_mode: 同期無効\n // tv_low_latency_mode: 同期無効\n panel_display_state: 'RestorePreviousState' | 'AlwaysDisplay' | 'AlwaysFold';\n tv_panel_active_tab: 'Program' | 'Channel' | 'Comment' | 'Twitter';\n tv_channel_selection_requires_alt_key: boolean;\n caption_font: string;\n always_border_caption_text: boolean;\n specify_caption_opacity: boolean;\n caption_opacity: number;\n tv_show_superimpose: boolean;\n // capture_copy_to_clipboard: 同期無効\n capture_save_mode: 'Browser' | 'UploadServer' | 'Both';\n capture_caption_mode: 'VideoOnly' | 'CompositingCaption' | 'Both';\n // sync_settings: 同期無効\n comment_speed_rate: number;\n comment_font_size: number;\n close_comment_form_after_sending: boolean;\n muted_comment_keywords: IMutedCommentKeywords[];\n muted_niconico_user_ids: string[];\n mute_vulgar_comments: boolean;\n mute_abusive_discriminatory_prejudiced_comments: boolean;\n mute_big_size_comments: boolean;\n mute_fixed_comments: boolean;\n mute_colored_comments: boolean;\n mute_consecutive_same_characters_comments: boolean;\n fold_panel_after_sending_tweet: boolean;\n reset_hashtag_when_program_switches: boolean;\n auto_add_watching_channel_hashtag: boolean;\n twitter_active_tab: 'Search' | 'Timeline' | 'Capture';\n tweet_hashtag_position: 'Prepend' | 'Append' | 'PrependWithLineBreak' | 'AppendWithLineBreak';\n tweet_capture_watermark_position: 'None' | 'TopLeft' | 'TopRight' | 'BottomLeft' | 'BottomRight';\n}\n\n/**\n * ミュート対象のコメントのキーワードのインターフェイス\n */\nexport interface IMutedCommentKeywords {\n match: 'partial' | 'forward' | 'backward' | 'exact' | 'regex';\n pattern: string;\n}\n\n\nclass Settings {\n\n /**\n * クライアント設定を取得する\n * @return クライアント設定 (取得に失敗した場合は null)\n */\n static async fetchClientSettings(): Promise {\n\n // API リクエストを実行\n const response = await APIClient.get('/settings/client');\n\n // エラー処理 (基本起こらないはず & 実行できなくても後続の処理に影響しないため何もしない)\n if ('is_error' in response) {\n return null;\n }\n\n return response.data;\n }\n\n\n /**\n * クライアント設定を更新する\n * @param settings クライアント設定\n */\n static async updateClientSettings(settings: IClientSettings): Promise {\n\n // API リクエストを実行\n // 正常時は 204 No Content が返るし、エラーは基本起こらないはずなので何もしない\n await APIClient.put('/settings/client', settings);\n }\n}\n\nexport default Settings;\n","\nimport { defineStore } from 'pinia';\n\nimport Settings, { IClientSettings, IMutedCommentKeywords } from '@/services/Settings';\nimport Utils from '@/utils';\n\n\n// LocalStorage に保存される KonomiTV の設定データ\ninterface ILocalClientSettings {\n pinned_channel_ids: string[];\n showed_panel_last_time: boolean;\n selected_twitter_account_id: number | null;\n saved_twitter_hashtags: string[];\n tv_streaming_quality: '1080p-60fps' | '1080p' | '810p' | '720p' | '540p' | '480p' | '360p' | '240p';\n tv_data_saver_mode: boolean;\n tv_low_latency_mode: boolean;\n panel_display_state: 'RestorePreviousState' | 'AlwaysDisplay' | 'AlwaysFold';\n tv_panel_active_tab: 'Program' | 'Channel' | 'Comment' | 'Twitter';\n tv_channel_selection_requires_alt_key: boolean;\n caption_font: string;\n always_border_caption_text: boolean;\n specify_caption_opacity: boolean;\n caption_opacity: number;\n tv_show_superimpose: boolean;\n capture_copy_to_clipboard: boolean;\n capture_save_mode: 'Browser' | 'UploadServer' | 'Both';\n capture_caption_mode: 'VideoOnly' | 'CompositingCaption' | 'Both';\n sync_settings: boolean;\n comment_speed_rate: number;\n comment_font_size: number;\n close_comment_form_after_sending: boolean;\n muted_comment_keywords: IMutedCommentKeywords[];\n muted_niconico_user_ids: string[];\n mute_vulgar_comments: boolean;\n mute_abusive_discriminatory_prejudiced_comments: boolean;\n mute_big_size_comments: boolean;\n mute_fixed_comments: boolean;\n mute_colored_comments: boolean;\n mute_consecutive_same_characters_comments: boolean;\n fold_panel_after_sending_tweet: boolean;\n reset_hashtag_when_program_switches: boolean;\n auto_add_watching_channel_hashtag: boolean;\n twitter_active_tab: 'Search' | 'Timeline' | 'Capture';\n tweet_hashtag_position: 'Prepend' | 'Append' | 'PrependWithLineBreak' | 'AppendWithLineBreak';\n tweet_capture_watermark_position: 'None' | 'TopLeft' | 'TopRight' | 'BottomLeft' | 'BottomRight';\n}\n\n// 設定データのうち、同期対象の設定キー\n// サーバー側の app.schemas.ClientSettings と\n// client/src/services/Settings.ts 内の IClientSettings で定義されているものと同じ\nconst sync_settings_keys = [\n 'pinned_channel_ids',\n // showed_panel_last_time: 同期無効\n // selected_twitter_account_id: 同期無効\n 'saved_twitter_hashtags',\n // tv_streaming_quality: 同期無効\n // tv_data_saver_mode: 同期無効\n // tv_low_latency_mode: 同期無効\n 'panel_display_state',\n 'tv_panel_active_tab',\n 'tv_channel_selection_requires_alt_key',\n 'caption_font',\n 'always_border_caption_text',\n 'specify_caption_opacity',\n 'caption_opacity',\n 'tv_show_superimpose',\n // capture_copy_to_clipboard: 同期無効\n 'capture_save_mode',\n 'capture_caption_mode',\n // sync_settings: 同期無効\n 'comment_speed_rate',\n 'comment_font_size',\n 'close_comment_form_after_sending',\n 'muted_comment_keywords',\n 'muted_niconico_user_ids',\n 'mute_vulgar_comments',\n 'mute_abusive_discriminatory_prejudiced_comments',\n 'mute_big_size_comments',\n 'mute_fixed_comments',\n 'mute_colored_comments',\n 'mute_consecutive_same_characters_comments',\n 'fold_panel_after_sending_tweet',\n 'reset_hashtag_when_program_switches',\n 'auto_add_watching_channel_hashtag',\n 'twitter_active_tab',\n 'tweet_hashtag_position',\n 'tweet_capture_watermark_position',\n];\n\n// LocalStorage に保存される KonomiTV の設定データのデフォルト値\nconst default_settings: ILocalClientSettings = {\n\n // ***** 設定画面から直接変更できない設定値 *****\n\n // ピン留めしているチャンネルの ID (ex: gr011) が入るリスト\n pinned_channel_ids: [],\n // 前回視聴画面を開いた際にパネルが表示されていたかどうか (同期無効)\n showed_panel_last_time: true,\n // 現在ツイート対象として選択されている Twitter アカウントの ID (同期無効)\n selected_twitter_account_id: null,\n // 保存している Twitter のハッシュタグが入るリスト\n saved_twitter_hashtags: [],\n\n // ***** 設定 → 全般 *****\n\n // テレビのデフォルトのストリーミング画質 (Default: 1080p) (同期無効)\n tv_streaming_quality: '1080p',\n // テレビを通信節約モードで視聴する (Default: オフ) (同期無効)\n tv_data_saver_mode: false,\n // テレビを低遅延で視聴する (Default: 低遅延で視聴する) (同期無効)\n tv_low_latency_mode: true,\n // デフォルトのパネルの表示状態 (Default: 前回の状態を復元する)\n panel_display_state: 'RestorePreviousState',\n // テレビをみるときにデフォルトで表示されるパネルのタブ (Default: 番組情報タブ)\n tv_panel_active_tab: 'Program',\n // チャンネル選局のキーボードショートカットを Alt or Option + 数字キー/テンキーに変更する (Default: オフ)\n tv_channel_selection_requires_alt_key: false,\n\n // ***** 設定 → 字幕 *****\n\n // 字幕のフォント (Default: Windows TV 丸ゴシック)\n caption_font: 'Windows TV MaruGothic',\n // 字幕の文字を常に縁取って描画する (Default: 常に縁取る)\n always_border_caption_text: true,\n // 字幕の不透明度を指定する (Default: 指定しない)\n specify_caption_opacity: false,\n // 字幕の不透明度 (Default: 50%)\n caption_opacity: 0.5,\n // テレビをみるときに文字スーパーを表示する (Default: 表示する)\n tv_show_superimpose: true,\n\n // ***** 設定 → キャプチャ *****\n\n // キャプチャをクリップボードにコピーする (Default: 無効) (同期無効)\n capture_copy_to_clipboard: false,\n // キャプチャの保存先 (Default: KonomiTV サーバーにアップロード)\n capture_save_mode: 'UploadServer',\n // 字幕が表示されているときのキャプチャの保存モード (Default: 映像のみのキャプチャと、字幕を合成したキャプチャを両方保存する)\n capture_caption_mode: 'Both',\n\n // ***** 設定 → アカウント *****\n\n // 設定を同期する (Default: 同期しない) (同期無効)\n sync_settings: false,\n\n // ***** 設定 → ニコニコ実況 *****\n\n // コメントの速さ (Default: 1倍)\n comment_speed_rate: 1,\n // コメントのフォントサイズ (Default: 34px)\n comment_font_size: 34,\n // コメント送信後にコメント入力フォームを閉じる (Default: オン)\n close_comment_form_after_sending: true,\n\n // ***** 設定 → ニコニコ実況 (ミュート設定) *****\n\n // ミュート済みのコメントのキーワードが入るリスト\n muted_comment_keywords: [],\n // ミュート済みのニコニコユーザー ID が入るリスト\n muted_niconico_user_ids: [],\n // 露骨な表現を含むコメントをミュートする (Default: ミュートする)\n mute_vulgar_comments: true,\n // 罵倒や誹謗中傷、差別的な表現、政治的に偏った表現を含むコメントをミュートする (Default: ミュートする)\n mute_abusive_discriminatory_prejudiced_comments: true,\n // 文字サイズが大きいコメントをミュートする (Default: ミュートする)\n mute_big_size_comments: true,\n // 映像の上下に固定表示されるコメントをミュートする (Default: ミュートしない)\n mute_fixed_comments: false,\n // 色付きのコメントをミュートする (Default: ミュートしない)\n mute_colored_comments: false,\n // 8文字以上同じ文字が連続しているコメントをミュートする (Default: ミュートしない)\n mute_consecutive_same_characters_comments: false,\n\n // ***** 設定 → Twitter *****\n\n // ツイート送信後にパネルを折りたたむ (Default: オフ)\n fold_panel_after_sending_tweet: false,\n // 番組が切り替わったときにハッシュタグフォームをリセットする (Default: オン)\n reset_hashtag_when_program_switches: true,\n // 視聴中のチャンネルに対応する局タグを自動で追加する (Default: オン)\n auto_add_watching_channel_hashtag: true,\n // デフォルトで表示される Twitter タブ内のタブ (Default: キャプチャタブ)\n twitter_active_tab: 'Capture',\n // ツイートにつけるハッシュタグの位置 (Default: ツイート本文の後に追加する)\n tweet_hashtag_position: 'Append',\n // ツイートするキャプチャに番組名の透かしを描画する (Default: 透かしを描画しない)\n tweet_capture_watermark_position: 'None',\n};\n\n/**\n * LocalStorage の KonomiTV-Settings キーから設定データを取得する\n * @returns 設定データ\n */\nexport function getLocalStorageSettings(): {[key: string]: any} {\n const settings = localStorage.getItem('KonomiTV-Settings');\n if (settings !== null) {\n return JSON.parse(settings);\n } else {\n // もし LocalStorage に KonomiTV-Settings キーがまだない場合、あらかじめデフォルトの設定値を保存しておく\n setLocalStorageSettings(default_settings);\n return default_settings;\n }\n}\n\n/**\n * LocalStorage の KonomiTV-Settings キーに設定データを保存する\n * @param settings 設定データ\n */\nexport function setLocalStorageSettings(settings: {[key: string]: any}): void {\n localStorage.setItem('KonomiTV-Settings', JSON.stringify(settings));\n}\n\n/**\n * 与えられた設定データを並び替えたり足りない設定キーを補完したり不要な設定キーを削除したりと整形して返す\n * @param settings 設定データ\n */\nfunction getNormalizedSettings(settings: {[key: string]: any}): ILocalClientSettings {\n\n // (名前が変わった、廃止されたなどの理由で) 現在の default_settings に存在しない設定キーを排除した上で並び替え\n // 並び替えられていないと設定データの比較がうまくいかない\n const new_settings: {[key: string]: any} = {};\n for (const default_settings_key of Object.keys(default_settings)) {\n if (default_settings_key in settings) {\n new_settings[default_settings_key] = settings[default_settings_key];\n } else {\n // 後のバージョンで追加されたなどの理由で現状の KonomiTV-Settings に存在しない設定キーの場合\n // その設定キーのデフォルト値を取得する\n new_settings[default_settings_key] = default_settings[default_settings_key];\n }\n }\n\n // この状態の新しい設定データを返す\n return new_settings as ILocalClientSettings;\n}\n\n/**\n * 設定データを共有するストア\n */\nconst useSettingsStore = defineStore('settings', {\n state: () => {\n\n // ref: https://www.vuemastery.com/blog/refresh-proof-your-pinia-stores/\n\n // LocalStorage から設定データを取得する\n const settings = getLocalStorageSettings();\n\n // (名前が変わった、廃止されたなどの理由で) 現在の default_settings に存在しない設定キーを排除した上で並び替え\n const new_settings = getNormalizedSettings(settings);\n\n // この状態の新しい設定データを LocalStorage に保存する\n setLocalStorageSettings(new_settings);\n\n // 設定データを Store の state のデフォルト値として返す\n return {\n settings: new_settings as ILocalClientSettings,\n };\n },\n actions: {\n\n /**\n * エクスポートした JSON ファイルから設定データをインポートする (既存の設定はすべて上書きされる)\n * @param file エクスポートした JSON ファイル\n * @returns インポートに成功したかどうか\n */\n async importClientSettings(file: File): Promise {\n\n // JSON ファイルを読み込む\n const settings_json = await file.text();\n\n // JSON ファイルをパースする\n let settings = {};\n try {\n settings = JSON.parse(settings_json);\n } catch (error) {\n return false;\n }\n\n // (名前が変わった、廃止されたなどの理由で) 現在の default_settings に存在しない設定キーを排除した上で並び替え\n const new_settings = getNormalizedSettings(settings);\n\n // この状態の新しい設定データを LocalStorage に保存し、Store の state に反映する\n // このとき、既存の設定データはすべて上書きされる\n setLocalStorageSettings(new_settings);\n this.settings = new_settings;\n\n // 設定データをサーバーに同期する\n await this.syncClientSettingsToServer();\n\n return true;\n },\n\n /**\n * 設定データを初期状態にリセットする\n */\n async resetClientSettings(): Promise {\n\n // デフォルトの設定に現在設定の同期がオンになっているかだけ反映した設定データ\n const default_settings_modified: ILocalClientSettings = {\n ...default_settings,\n sync_settings: this.settings.sync_settings,\n };\n\n // デフォルト値の設定データを LocalStorage に保存し、Store の state に反映する\n setLocalStorageSettings(default_settings_modified);\n this.settings = default_settings_modified;\n\n // 設定データをサーバーに同期する\n await this.syncClientSettingsToServer();\n },\n\n /**\n * 設定データのうち、サーバーへの同期対象の設定キーのみで構成されたオブジェクト (IClientSettings と一致する) を返す\n * @returns サーバーへの同期対象の設定キーのみで構成されたオブジェクト\n */\n getSyncableClientSettings(): IClientSettings {\n\n // 同期対象の設定キーのみで設定データをまとめ直す\n // sync_settings には同期対象外の設定は含まれない\n const sync_settings: {[key: string]: any} = {};\n for (const sync_settings_key of sync_settings_keys) {\n if (sync_settings_key in this.settings) {\n sync_settings[sync_settings_key] = this.settings[sync_settings_key];\n } else {\n // 後から追加された設定キーなどの理由で設定キーが現状の KonomiTV-Settings に存在しない場合\n // その設定キーのデフォルト値を取得する\n sync_settings[sync_settings_key] = default_settings[sync_settings_key];\n }\n }\n\n return sync_settings as IClientSettings;\n },\n\n /**\n * ログイン時かつ同期が有効な場合、サーバーに保存されている設定データをこのクライアントに同期する\n * @param force ログイン中なら同期が有効かに関わらず実行する (デフォルト: false)\n */\n async syncClientSettingsFromServer(force: boolean = false): Promise {\n\n // ログインしていない時、同期が無効なときは実行しない\n if (Utils.getAccessToken() === null || (this.settings.sync_settings === false && force === false)) {\n return;\n }\n\n // サーバーから設定データをダウンロード\n const settings_server = await Settings.fetchClientSettings();\n if (settings_server === null) {\n return; // 取得できなくても後続の処理には影響しないので、サイレントに失敗する\n }\n\n // クライアントの設定データをサーバーからの設定データで上書き\n for (const [settings_server_key, settings_server_value] of Object.entries(settings_server)) {\n this.settings[settings_server_key] = settings_server_value;\n }\n },\n\n /**\n * ログイン時かつ同期が有効な場合、このクライアントの設定をサーバーに同期する\n * @param force ログイン中なら同期が有効かに関わらず実行する (デフォルト: false)\n */\n async syncClientSettingsToServer(force: boolean = false): Promise {\n\n // ログインしていない時、同期が無効なときは実行しない\n if (Utils.getAccessToken() === null || (this.settings.sync_settings === false && force === false)) {\n return;\n }\n\n // 同期対象の設定キーのみで設定データをまとめ直す\n const sync_settings = this.getSyncableClientSettings();\n\n // サーバーに設定データをアップロード\n await Settings.updateClientSettings(sync_settings);\n }\n }\n});\n\nexport default useSettingsStore;\n","\nimport { Buffer } from 'buffer';\n\nimport useSettingsStore from '@/store/SettingsStore';\n\n\n/**\n * コメント周りのユーティリティ\n */\nexport class CommentUtils {\n\n // 「露骨な表現を含むコメントをミュートする」のフィルタ正規表現\n static readonly mute_vulgar_comments_pattern = new RegExp(Buffer.from('XChpXCl8XChVXCl8cHJwcnzvvZDvvZLvvZDvvZJ8U0VYfFPjgIdYfFPil69YfFPil4tYfFPil49YfO+8s++8pe+8uHzvvLPjgIfvvLh877yz4pev77y4fO+8s+KXi++8uHzvvLPil4/vvLh844Ki44OA44Or44OIfOOCouODiuOCpXzjgqLjg4rjg6t844Kk44Kr6IetfOOCpOOBj3zjgYbjgpPjgZN844Km44Oz44KzfOOBhuOCk+OBoXzjgqbjg7Pjg4F844Ko44Kt44ObfOOBiOOBoeOBiOOBoXzjgYjjgaPjgaF844Ko44OD44OBfOOBiOOBo+OCjXzjgqjjg4Pjg61844GI44KNfOOCqOODrXzlt6Xlj6N844GK44GV44KP44KK44G+44KTfOOBiuOBl+OBo+OBk3zjgqrjgrfjg4PjgrN844Kq44OD44K144OzfOOBiuOBo+OBseOBhHzjgqrjg4Pjg5HjgqR844Kq44OK44OL44O8fOOBiuOBquOBu3zjgqrjg4rjg5t844GK44Gx44GEfOOCquODkeOCpHzjgYpwfOOBiu+9kHzjgqrjg5Xjg5HjgrN844Ks44Kk44K444OzfOOCreODs+OCv+ODnnzjgY/jgbHjgYJ844GP44Gx44GBfOOCr+ODquODiOODquOCuXzjgq/jg7Pjg4t844GU44GP44GU44GP44GU44GP44GU44GPfOOCs+ODs+ODieODvOODoHzjgZHjgaTjgYLjgap844Kx44OE44Ki44OKfOOCtuODvOODoeODs3zjgrfjgrN844GX44GT44GX44GTfOOCt+OCs+OCt+OCs3zjgZnjgZHjgZnjgZF844Gb44GE44GI44GNfOOBm+OBhOOCinzjgZvjg7zjgop844GZ44GF44GF44GF44GF44GFfOOBmeOBhuOBhuOBhuOBhuOBhnzjgrvjgq/jg63jgrl844K744OD44Kv44K5fOOCu+ODleODrHzjgaHjgaPjgbHjgYR844Gh44Gj44OR44KkfOODgeODg+ODkeOCpHzjgaHjgpPjgZN844Gh44CH44GTfOOBoeKXr+OBk3zjgaHil4vjgZN844Gh4peP44GTfOODgeODs+OCs3zjg4HjgIfjgrN844OB4pev44KzfOODgeKXi+OCs3zjg4Hil4/jgrN844Gh44KT44G9fOOBoeOAh+OBvXzjgaHil6/jgb1844Gh4peL44G9fOOBoeKXj+OBvXzjg4Hjg7Pjg51844OB44CH44OdfOODgeKXr+ODnXzjg4Hil4vjg51844OB4peP44OdfOOBoeOCk+OBoeOCk3zjg4Hjg7Pjg4Hjg7N844Gm44GD44KT44Gm44GD44KTfOODhuOCo+ODs+ODhuOCo+ODs3zjg4bjgqPjg7Pjg51844OH44Kr44GEfOODh+ODquODmOODq3zjgarjgYvjgaDjgZd844Gq44GL44CH44GXfOOBquOBi+KXr+OBl3zjgarjgYvil4vjgZd844Gq44GL4peP44GXfOiEseOBknzjg4zjgYR844OM44GLfOODjOOCq3zjg4zjgY1844OM44KtfOODjOOBj3zjg4zjgq9844OM44GRfOODjOOCsXzjg4zjgZN844OM44KzfOOBseOBhOOCguOBv3zjg5Hjg5HmtLt844G144GG44O7fOOBteOBhuKApnzjgbXjgYV8776M772pfOOBteOBj+OCieOBv3zjgbXjgY/jgonjgpPjgad844G644Gj44GffOOBuuOCjeOBuuOCjXzjg5rjg63jg5rjg618776N776f776b776N776f776bfOODleOCp+ODqXzjgbvjgYbjgZHjgYR844G844Gj44GNfOODneODq+ODjnzjgbzjgo3jgpN844Oc44Ot44OzfO++ju++nu++m+++nXzjgb3jgo3jgop844Od44Ot44OqfO++ju++n+++m+++mHzjg57jg7PjgY3jgaR844Oe44Oz44Kt44OEfOOBvuOCk+OBk3zjgb7jgIfjgZN844G+4pev44GTfOOBvuKXi+OBk3zjgb7il4/jgZN844Oe44Oz44KzfOODnuOAh+OCs3zjg57il6/jgrN844Oe4peL44KzfOODnuKXj+OCs3zjgb7jgpPjgZXjgpN844KC44Gj44GT44KKfOODouODg+OCs+ODqnzjgoLjgb/jgoLjgb9844Oi44Of44Oi44OffOODpOOBo+OBn3zjg6TjgaPjgaZ844Ok44KJfOOChOOCieOBm+OCjXzjg6Tjgop844Ok44KLfOODpOOCjHzjg6Tjgo1844Op44OW44ObfOODr+ODrOODoXzmhJvmtrJ85ZaYfOmZsOaguHzpmbDojI586Zmw5ZSHfOa3q+WkonzpmqDmr5t86Zmw5q+bfOeUo+OCgeOCi3zlpbPjga7lrZDjga7ml6V85rGa44Gj44GV44KTfOWnpnzpqI7kuZfkvY185beo5qC5fOW3qOODgeODs3zlt6jnj4186YeR546JfOaciOe1jHzlvozog4zkvY185a2Q56iufOWtkOS9nOOCinzlsITnsr585L+h6ICFfOeyvua2snzpgI/jgZF85oCn5LqkfOeyvuWtkHzmraPluLjkvY185oCn5b60fOaAp+eahHznlJ/nkIZ85a+45q2i44KBfOe0oOadkHzmirHjgYR85oqx44GLfOaKseOBjXzmirHjgY985oqx44GRfOaKseOBk3zkvZPmtrJ85Lmz6aaWfOaBpeWeonznj43mo5J85Lit44Gg44GXfOS4reWHuuOBl3zlsL985oqc44GEfOaKnOOBkeOBquOBhHzmipzjgZHjgot85oqc44GR44KMfOeKr+ePjXzohqjjgol85YyF6IyOfOWLg+i1t3zmkannvoV86a2U576FfOaPieOBvnzmj4njgb985o+J44KAfOaPieOCgXzmvKvmuZZ844CH772efOKXr++9nnzil4vvvZ584peP772efOOAh+ODg+OCr+OCuXzil6/jg4Pjgq/jgrl84peL44OD44Kv44K5fOKXj+ODg+OCr+OCuQ==', 'base64').toString());\n\n // 「罵倒や差別的な表現を含むコメントをミュートする」のフィルタ正規表現\n static readonly mute_abusive_discriminatory_prejudiced_comments_pattern = new RegExp(Buffer.from('44CCfOOCouODi+ODl+ODrOOBj+OCk3zjgqLjg4vjg5fjg6zlkJt844Ki44K544OafOOCpOOCq+OCjHzjgYTjgb7jgYTjgaF844Kk44Oe44Kk44OBfOOCpOODqeOBpOOBj3zjgqbjgrh844Km44O844OofOOCpuODqHzjgqbjg6jjgq9844Km44OyfOOBjeOCguOBhHzjgq3jg6LjgqR844Kt44Oi44GEfOOCrS/jg6Av44OBfOOCrOOCpOOCuHzvvbbvvp7vvbLvvbzvvp5844Ks44KtfOOCq+OCuXzjgq3jg4Pjgrp844GN44Gh44GM44GEfOOCreODgeOCrOOCpHzjgq3jg6Djg4F844K344OKfOOCueODhuODnnzjgaTjgb7jgonjgap844Gk44G+44KJ44KTfOODgeODp+ODg+ODkeODqnzjg4Hjg6fjg7N85Y2D44On44OzfOOBpOOCk+OBvHzjg4Tjg7Pjg5x844ON44OI44Km44OofOOBq+OBoOOBguOBgnzjg4vjg4B85LqM44OAfO++hu++gO++nnzjg5Hjg7zjg6h844OR44OofOODkeODqOOCr3zjgbbjgaPjgZV844OW44OD44K1fOOBtuOBleOBhHzjg5bjgrXjgqR844G+44Gs44GRfOODoeOCr+ODqXzjg5Djgqt844Og44Kr44Gk44GPfOiNkuOCieOBl3zpurvnlJ/jgrvjg6Hjg7Pjg4h85oWw5a6J5ammfOWus+WFkHzlpJblrZd85aem5Zu9fOmfk+WbvXzpn5PkuK186Z+T5pelfOWfuuWcsOWklnzmsJfni4LjgYR85rCX6YGV44GEfOWIh+OBo+OBn3zliIfjgaPjgaZ85rCX5oyB44Gh5oKqfOWbveS6pOaWree1tnzmrrp86aCDfOmgg+OBl3zpoIPjgZl86aCD44GbfOWcqOaXpXzlj4LmlL/mqKl85q2744GtfOawj+OBrXzvvoDvvot85q255YyVfOatueODknzlpLHpgJ986Zqc5a6zfOaWreS6pHzkuK3pn5N85pyd6a6ufOW+tOeUqOW3pXzlo7p85aO3fOWjvHzml6Xpn5N85pel5bidfOeymOedgHzlj43ml6V86aas6bm/fOeZuueLgnznmbrpgZR85py0fOWjsuWbvXzkuI3lv6t85L215ZCIfOmWk+aKnOOBkXzmloflj6V86Z2W5Zu9', 'base64').toString());\n\n // 「8文字以上同じ文字が連続しているコメントをミュートする」のフィルタ正規表現\n static readonly mute_consecutive_same_characters_comments_pattern = /(.)\\1{7,}/;\n\n // ニコ生の特殊コマンド付きコメントのフィルタ正規表現\n static readonly special_command_comments_pattern = /\\/[a-z]+ /;\n\n // 迷惑な統計コメントのフィルタ正規表現\n static readonly annoying_statistical_comments_pattern = /最高\\d+米\\/|計\\d+ID|総\\d+米/;\n\n // ニコニコの色指定を 16 進数カラーコードに置換するテーブル\n static readonly color_table: {[key: string]: string} = {\n 'white': '#FFEAEA',\n 'red': '#F02840',\n 'pink': '#FD7E80',\n 'orange': '#FDA708',\n 'yellow': '#FFE133',\n 'green': '#64DD17',\n 'cyan': '#00D4F5',\n 'blue': '#4763FF',\n 'purple': '#D500F9',\n 'black': '#1E1310',\n 'white2': '#CCCC99',\n 'niconicowhite': '#CCCC99',\n 'red2': '#CC0033',\n 'truered': '#CC0033',\n 'pink2': '#FF33CC',\n 'orange2': '#FF6600',\n 'passionorange': '#FF6600',\n 'yellow2': '#999900',\n 'madyellow': '#999900',\n 'green2': '#00CC66',\n 'elementalgreen': '#00CC66',\n 'cyan2': '#00CCCC',\n 'blue2': '#3399FF',\n 'marineblue': '#3399FF',\n 'purple2': '#6633CC',\n 'nobleviolet': '#6633CC',\n 'black2': '#666666',\n };\n\n\n /**\n * ニコニコの色指定を 16 進数カラーコードに置換する\n * @param color ニコニコの色指定\n * @return 16 進数カラーコード\n */\n static getCommentColor(color: string): string | null {\n return this.color_table[color] || null;\n }\n\n\n /**\n * ニコニコの位置指定を DPlayer の位置指定に置換する\n * @param position ニコニコの位置指定\n * @return DPlayer の位置指定\n */\n static getCommentPosition(position: string): 'top' | 'right' | 'bottom' | null {\n switch (position) {\n case 'ue':\n return 'top';\n case 'naka':\n return 'right';\n case 'shita':\n return 'bottom';\n default:\n return null;\n }\n }\n\n\n /**\n * ニコニコのサイズ指定を DPlayer のサイズ指定に置換する\n * @param size ニコニコのサイズ指定\n * @returns DPlayer のサイズ指定\n */\n static getCommentSize(size: string): 'big' | 'medium' | 'small' | null {\n switch (size) {\n case 'big':\n case 'medium':\n case 'small':\n return size;\n default:\n return null;\n }\n }\n\n\n /**\n * ニコニコのコメントコマンドを解析する\n * @param comment_mail ニコニコのコメントコマンド\n * @returns コメントの色、位置、サイズ\n */\n static parseCommentCommand(comment_mail: string): {\n color: string;\n position: 'top' | 'right' | 'bottom';\n size: 'big' | 'medium' | 'small';\n } {\n let color = '#FFEAEA';\n let position: 'top' | 'right' | 'bottom' = 'right';\n let size: 'big' | 'medium' | 'small' = 'medium';\n\n if (comment_mail !== undefined && comment_mail !== null) {\n const commands = comment_mail.replace('184', '').split(' ');\n\n for (const command of commands) {\n const parsed_color = CommentUtils.getCommentColor(command);\n const parsed_position = CommentUtils.getCommentPosition(command);\n const parsed_size = CommentUtils.getCommentSize(command);\n if (parsed_color !== null) {\n color = parsed_color;\n }\n if (parsed_position !== null) {\n position = parsed_position;\n }\n if (parsed_size !== null) {\n size = parsed_size;\n }\n }\n }\n\n return {color, position, size};\n }\n\n\n /**\n * ミュート対象のコメントかどうかを判断する\n * @param comment コメント\n * @param user_id コメントを投稿したユーザーの ID\n * @param color コメントの色\n * @param position コメントの位置\n * @param size コメントのサイズ\n * @return ミュート対象のコメントなら true を返す\n */\n static isMutedComment(\n comment: string,\n user_id: string,\n color?: string,\n position?: 'top' | 'right' | 'bottom',\n size?: 'big' | 'medium' | 'small',\n ): boolean {\n\n const settings_store = useSettingsStore();\n\n // ユーザー ID ミュート処理\n if (settings_store.settings.muted_niconico_user_ids.includes(user_id)) {\n return true;\n }\n\n // ニコ生の特殊コマンド付きコメント (/nicoad, /emotion など) を一括で弾く\n if (CommentUtils.special_command_comments_pattern.test(comment)) {\n return true;\n }\n\n // 「映像の上下に固定表示されるコメントをミュートする」がオンの場合\n // コメントの位置が top (上固定) もしくは bottom (下固定) のときは弾く\n if (settings_store.settings.mute_fixed_comments === true && (position === 'top' || position === 'bottom')) {\n console.log('[CommentUtils] Muted comment (fixed_comments): ' + comment);\n return true;\n }\n\n // 「色付きのコメントをミュートする」がオンの場合\n // コメントの色が #FFEAEA (デフォルト) 以外のときは弾く\n if (settings_store.settings.mute_colored_comments === true && color !== '#FFEAEA') {\n console.log('[CommentUtils] Muted comment (colored_comments): ' + comment);\n return true;\n }\n\n // 「文字サイズが大きいコメントをミュートする」がオンの場合\n // コメントのサイズが big のときは弾く\n if (settings_store.settings.mute_big_size_comments === true && size === 'big') {\n console.log('[CommentUtils] Muted comment (big_size_comments): ' + comment);\n return true;\n }\n\n // 「露骨な表現を含むコメントをミュートする」がオンの場合\n if ((settings_store.settings.mute_vulgar_comments === true) &&\n (CommentUtils.mute_vulgar_comments_pattern.test(comment))) {\n console.log('[CommentUtils] Muted comment (vulgar_comments): ' + comment);\n return true;\n }\n\n // 「罵倒や差別的な表現を含むコメントをミュートする」がオンの場合\n if ((settings_store.settings.mute_abusive_discriminatory_prejudiced_comments === true) &&\n (CommentUtils.mute_abusive_discriminatory_prejudiced_comments_pattern.test(comment))) {\n console.log('[CommentUtils] Muted comment (abusive_discriminatory_prejudiced_comments): ' + comment);\n return true;\n }\n\n // 「8文字以上同じ文字が連続しているコメントをミュートする」がオンの場合\n if ((settings_store.settings.mute_consecutive_same_characters_comments === true &&\n (CommentUtils.mute_consecutive_same_characters_comments_pattern.test(comment)))) {\n console.log('[CommentUtils] Muted comment (consecutive_same_characters_comments): ' + comment);\n return true;\n }\n\n // キーワードミュート処理\n for (const muted_comment_keyword of settings_store.settings.muted_comment_keywords) {\n if (muted_comment_keyword.pattern === '') continue; // キーワードが空文字のときは無視\n switch (muted_comment_keyword.match) {\n // 部分一致\n case 'partial':\n if (comment.includes(muted_comment_keyword.pattern)) {\n console.log('[CommentUtils] Muted comment (partial): ' + comment);\n return true;\n }\n break;\n // 前方一致\n case 'forward':\n if (comment.startsWith(muted_comment_keyword.pattern)) {\n console.log('[CommentUtils] Muted comment (forward): ' + comment);\n return true;\n }\n break;\n // 後方一致\n case 'backward':\n if (comment.endsWith(muted_comment_keyword.pattern)) {\n console.log('[CommentUtils] Muted comment (backward): ' + comment);\n return true;\n }\n break;\n // 完全一致\n case 'exact':\n if (comment === muted_comment_keyword.pattern) {\n console.log('[CommentUtils] Muted comment (exact): ' + comment);\n return true;\n }\n break;\n // 正規表現\n case 'regex':\n if (new RegExp(muted_comment_keyword.pattern).test(comment)) {\n console.log('[CommentUtils] Muted comment (regex): ' + comment);\n return true;\n }\n break;\n }\n }\n\n // 「NHK→計1447ID/内プレ425ID/総33372米 ◆ Eテレ → 計73ID/内プレ19ID/総941米」のような\n // 迷惑コメントを一括で弾く (あえてミュートしたくないユースケースが思い浮かばないのでデフォルトで弾く)\n // 一番最後なのは、この迷惑コメント自体の頻度が低いため\n if (CommentUtils.annoying_statistical_comments_pattern.test(comment)) {\n return true;\n }\n\n // いずれのミュート処理にも引っかからなかった (ミュート対象ではない)\n return false;\n }\n\n\n /**\n * ミュート済みキーワードリストに追加する (完全一致)\n * @param comment コメント文字列\n */\n static addMutedKeywords(comment: string): void {\n\n // すでにまったく同じミュート済みキーワードが追加済みの場合は何もしない\n const settings_store = useSettingsStore();\n for (const muted_comment_keyword of settings_store.settings.muted_comment_keywords) {\n if (muted_comment_keyword.match === 'exact' && muted_comment_keyword.pattern === comment) {\n return;\n }\n }\n\n // ミュート済みキーワードリストに追加\n settings_store.settings.muted_comment_keywords.push({\n match: 'exact',\n pattern: comment,\n });\n }\n\n\n /**\n * ミュート済みニコニコユーザー ID リストに追加する\n * @param user_id ニコニコユーザー ID\n */\n static addMutedNiconicoUserIDs(user_id: string): void {\n\n // すでに追加済みの場合は何もしない\n const settings_store = useSettingsStore();\n if (settings_store.settings.muted_niconico_user_ids.includes(user_id)) {\n return;\n }\n\n // ミュート済みニコニコユーザー ID リストに追加\n settings_store.settings.muted_niconico_user_ids.push(user_id);\n }\n}\n","\n/**\n * プレイヤー周りのユーティリティ\n */\nexport class PlayerUtils {\n\n /**\n * プレイヤーの背景画像をランダムで取得し、その URL を返す\n * @returns ランダムで設定されたプレイヤーの背景画像の URL\n */\n static generatePlayerBackgroundURL(): string {\n const background_count = 50; // 50種類から選択\n const random = (Math.floor(Math.random() * background_count) + 1);\n return `/assets/images/player-backgrounds/${random.toString().padStart(2, '0')}.jpg`;\n }\n\n\n /**\n * 現在のブラウザで H.265 / HEVC 映像が再生できるかどうかを取得する\n * ref: https://github.com/StaZhu/enable-chromium-hevc-hardware-decoding#mediacapabilities\n * @returns 再生できるなら true、できないなら false\n */\n static isHEVCVideoSupported(): boolean {\n // hvc1.1.6.L123.B0 の部分は呪文 (HEVC であることと、そのプロファイルを示す値らしい)\n return document.createElement('video').canPlayType('video/mp4; codecs=\"hvc1.1.6.L123.B0\"') === 'probably';\n }\n}\n","\nimport dayjs from 'dayjs';\nimport 'dayjs/locale/ja';\nimport isBetween from 'dayjs/plugin/isBetween';\nimport isSameOrAfter from 'dayjs/plugin/isSameOrAfter';\nimport isSameOrBefore from 'dayjs/plugin/isSameOrBefore';\n\nimport { IProgram } from '@/services/Programs';\nimport Utils from '@/utils';\n\n\n/**\n * 番組情報周りのユーティリティ\n */\nexport class ProgramUtils {\n\n /**\n * 番組情報中の[字]や[解]などの記号をいい感じに装飾する\n * @param program 番組情報のオブジェクト\n * @param key 番組情報のオブジェクトから取り出すプロパティのキー\n * @returns 装飾した文字列\n */\n static decorateProgramInfo(program: IProgram | null, key: string): string {\n\n // program が空でないかつ、program[key] が存在する\n if (program !== null && program[key] !== null) {\n\n // 番組情報に含まれる HTML の特殊文字で表示がバグらないように、事前に HTML エスケープしておく\n const text = Utils.escapeHTML(program[key]);\n\n // 本来 ARIB 外字である記号の一覧\n // ref: https://ja.wikipedia.org/wiki/%E7%95%AA%E7%B5%84%E8%A1%A8\n // ref: https://github.com/xtne6f/EDCB/blob/work-plus-s/EpgDataCap3/EpgDataCap3/ARIB8CharDecode.cpp#L1319\n const mark = '新|終|再|交|映|手|声|多|副|字|文|CC|OP|二|S|B|SS|無|無料' +\n 'C|S1|S2|S3|MV|双|デ|D|N|W|P|H|HV|SD|天|解|料|前|後初|生|販|吹|PPV|' +\n '演|移|他|収|・|英|韓|中|字/日|字/日英|3D|2K|4K|8K|5.1|7.1|22.2|60P|120P|d|HC|HDR|SHV|UHD|VOD|配|初';\n\n // 正規表現を作成\n const pattern1 = new RegExp('\\\\((二|字|再)\\\\)', 'g'); // 通常の括弧で囲まれている記号\n const pattern2 = new RegExp(`\\\\[(${mark})\\\\]`, 'g');\n\n // 正規表現で置換した結果を返す\n return text.replace(pattern1, '$1')\n .replace(pattern2, '$1');\n\n // 番組情報がない時間帯\n } else {\n\n dayjs.extend(isSameOrAfter);\n dayjs.extend(isSameOrBefore);\n dayjs.extend(isBetween);\n\n // 23時~翌7時 (0:00 ~ 06:59 or 23:00 ~ 23:59) の間なら放送を休止している可能性が高いので、放送休止と表示する\n const now = dayjs();\n const pause_time_start = dayjs().hour(0).minute(0).second(0);\n const pause_time_end = dayjs().hour(6).minute(59).second(59);\n const pause_time_start_23 = dayjs().hour(23).minute(0).second(0);\n const pause_time_end_23 = dayjs().hour(23).minute(59).second(59);\n if ((now.isSameOrAfter(pause_time_start) && now.isSameOrBefore(pause_time_end)) ||\n (now.isSameOrAfter(pause_time_start_23) && now.isSameOrBefore(pause_time_end_23))) {\n if (key === 'title') {\n return '放送休止'; // タイトル\n } else {\n return 'この時間は放送を休止しています。'; // 番組概要\n }\n\n // それ以外の時間帯では、「番組情報がありません」と表示する\n // 急な番組変更の影響で、一時的にその時間帯に対応する番組情報が消えることがある\n // 特に Mirakurun バックエンドでは高頻度で収集した EIT[p/f] が比較的すぐ反映されるため、この現象が起こりやすい\n // 日中に放送休止(停波)になることはまずあり得ないので、番組情報が取得できてないだけで視聴できるかも?というニュアンスを与える\n } else {\n if (key === 'title') {\n return '番組情報がありません'; // タイトル\n } else {\n return 'この時間の番組情報を取得できませんでした。'; // 番組概要\n }\n }\n }\n }\n\n\n /**\n * オブジェクトからプロパティを取得し、もしプロパティが存在しなければ代替値を返す\n * @param items 対象のオブジェクト\n * @param key オブジェクトから取り出すプロパティのキー\n * @param default_value 取得できなかった際の代替値\n * @returns オブジェクト取得した値 or 代替値\n */\n static getAttribute(items: {[key: string]: any}, key: string, default_value: any): any {\n\n // items が空でないかつ、items[key] が存在する\n if (items !== null && items[key] !== undefined && items[key] !== null) {\n\n // items[key] の内容を返す\n return items[key];\n\n // 指定された代替値を返す\n } else {\n return default_value;\n }\n }\n\n\n /**\n * 番組の進捗状況を取得する\n * @param program 番組情報\n * @returns 番組の進捗状況(%単位)\n */\n static getProgramProgress(program: IProgram): number {\n\n // program が空でない\n if (program !== null) {\n\n // 番組開始時刻から何秒進んだか\n const progress = dayjs(dayjs()).diff(program.start_time, 'second');\n\n // %単位の割合を算出して返す\n return progress / program.duration * 100;\n\n // 放送休止中\n } else {\n return 0;\n }\n }\n\n\n /**\n * 番組の放送時刻を取得する\n * @param program 番組情報\n * @param is_short 時刻のみ返すかどうか\n * @returns 番組の放送時刻\n */\n static getProgramTime(program: IProgram, is_short: boolean = false): string {\n\n // program が空でなく、かつ番組時刻が初期値でない\n if (program !== null && program.start_time !== '2000-01-01T00:00:00+09:00') {\n\n // dayjs で日付を扱いやすく\n dayjs.locale('ja'); // ロケールを日本に設定\n const start_time = dayjs(program.start_time);\n const end_time = dayjs(program.end_time);\n const duration = program.duration / 60; // 分換算\n\n // 時刻のみ返す\n if (is_short === true) { // 時刻のみ\n return `${start_time.format('HH:mm')} ~ ${end_time.format('HH:mm')}`;\n // 通常\n } else {\n return `${start_time.format('YYYY/MM/DD (dd) HH:mm')} ~ ${end_time.format('HH:mm')} (${duration}分)`;\n }\n\n // 放送休止中\n } else {\n\n // 時刻のみ返す\n if (is_short === true) {\n return '--:-- ~ --:--';\n // 通常\n } else {\n return '----/--/-- (-) --:-- ~ --:-- (--分)';\n }\n }\n }\n}\n","\n// 共通ユーティリティをデフォルトとしてインポート\nimport Utils from '@/utils/Utils';\nexport default Utils;\n\n// Utils フォルダ配下のユーティリティを一括でインポートできるように\nexport * from '@/utils/ChannelUtils';\nexport * from '@/utils/CommentUtils';\nexport * from '@/utils/PlayerUtils';\nexport * from '@/utils/ProgramUtils';\n","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"route-container\"},[_c('Header'),_c('main',[_c('Navigation'),_c('div',{staticClass:\"login-container-wrapper d-flex align-center w-100 mb-13\"},[_c('v-card',{staticClass:\"login-container px-10 pt-8 pb-11 mx-auto background lighten-1\",attrs:{\"elevation\":\"10\",\"width\":\"100%\",\"max-width\":\"450\"}},[_c('v-card-title',{staticClass:\"login__logo flex-column justify-center\"},[_c('v-img',{attrs:{\"max-width\":\"250\",\"src\":\"/assets/images/logo.svg\"}}),_c('h4',{staticClass:\"mt-10\"},[_vm._v(\"ログイン\")])],1),_c('v-divider'),_c('v-form',{ref:\"login\",on:{\"submit\":function($event){$event.preventDefault();}}},[_c('v-text-field',{staticClass:\"mt-12\",attrs:{\"outlined\":\"\",\"placeholder\":\"ユーザー名\",\"hide-details\":\"\",\"autofocus\":\"\",\"dense\":_vm.is_form_dense},model:{value:(_vm.username),callback:function ($$v) {_vm.username=$$v},expression:\"username\"}}),_c('v-text-field',{staticClass:\"mt-8\",attrs:{\"outlined\":\"\",\"placeholder\":\"パスワード\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"type\":_vm.password_showing ? 'text' : 'password',\"append-icon\":_vm.password_showing ? 'mdi-eye' : 'mdi-eye-off'},on:{\"click:append\":function($event){_vm.password_showing = !_vm.password_showing}},model:{value:(_vm.password),callback:function ($$v) {_vm.password=$$v},expression:\"password\"}}),_c('v-btn',{staticClass:\"login-button mt-5\",attrs:{\"color\":\"secondary\",\"depressed\":\"\",\"width\":\"100%\",\"height\":\"56\"},on:{\"click\":function($event){return _vm.login()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fa:sign-in\"}}),_vm._v(\"ログイン \")],1)],1)],1)],1)],1)],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('header',{staticClass:\"header\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"konomitv-logo ml-3 ml-md-6\",attrs:{\"to\":\"/tv/\"}},[_c('img',{staticClass:\"konomitv-logo__image\",attrs:{\"src\":\"/assets/images/logo.svg\",\"height\":\"21\"}})]),_c('v-spacer')],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","import { render, staticRenderFns } from \"./Header.vue?vue&type=template&id=84897154&scoped=true&\"\nvar script = {}\nimport style0 from \"./Header.vue?vue&type=style&index=0&id=84897154&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"84897154\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',[_c('div',{staticClass:\"navigation-container elevation-8\"},[_c('nav',{staticClass:\"navigation\"},[_c('div',{staticClass:\"navigation-scroll\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"navigation__link\",attrs:{\"active-class\":\"navigation__link--active\",\"to\":\"/tv/\"}},[_c('Icon',{staticClass:\"navigation__link-icon\",attrs:{\"icon\":\"fluent:tv-20-regular\",\"width\":\"26px\"}}),_c('span',{staticClass:\"navigation__link-text\"},[_vm._v(\"テレビをみる\")])],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"navigation__link\",attrs:{\"active-class\":\"navigation__link--active\",\"to\":\"/videos/\"}},[_c('Icon',{staticClass:\"navigation__link-icon\",attrs:{\"icon\":\"fluent:movies-and-tv-20-regular\",\"width\":\"26px\"}}),_c('span',{staticClass:\"navigation__link-text\"},[_vm._v(\"ビデオをみる\")])],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"navigation__link\",attrs:{\"active-class\":\"navigation__link--active\",\"to\":\"/timetable/\"}},[_c('Icon',{staticClass:\"navigation__link-icon\",attrs:{\"icon\":\"fluent:calendar-ltr-20-regular\",\"width\":\"26px\"}}),_c('span',{staticClass:\"navigation__link-text\"},[_vm._v(\"番組表\")])],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"navigation__link\",attrs:{\"active-class\":\"navigation__link--active\",\"to\":\"/reserves/\"}},[_c('Icon',{staticClass:\"navigation__link-icon\",staticStyle:{\"padding\":\"0.5px\"},attrs:{\"icon\":\"fluent:timer-16-regular\",\"width\":\"26px\"}}),_c('span',{staticClass:\"navigation__link-text\"},[_vm._v(\"録画予約\")])],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"navigation__link\",attrs:{\"active-class\":\"navigation__link--active\",\"to\":\"/mylist/\"}},[_c('Icon',{staticClass:\"navigation__link-icon\",attrs:{\"icon\":\"ic:round-playlist-play\",\"width\":\"26px\"}}),_c('span',{staticClass:\"navigation__link-text\"},[_vm._v(\"マイリスト\")])],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"navigation__link\",attrs:{\"active-class\":\"navigation__link--active\",\"to\":\"/captures/\"}},[_c('Icon',{staticClass:\"navigation__link-icon\",attrs:{\"icon\":\"fluent:image-multiple-24-regular\",\"width\":\"26px\"}}),_c('span',{staticClass:\"navigation__link-text\"},[_vm._v(\"キャプチャ\")])],1),_c('v-spacer'),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"navigation__link\",attrs:{\"active-class\":\"navigation__link--active\",\"to\":\"/settings/\"}},[_c('Icon',{staticClass:\"navigation__link-icon\",attrs:{\"icon\":\"fluent:settings-20-regular\",\"width\":\"26px\"}}),_c('span',{staticClass:\"navigation__link-text\"},[_vm._v(\"設定\")])],1),_c('a',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.top\",value:(_vm.versionStore.is_update_available ?\n `アップデートがあります (version ${_vm.versionStore.latest_version})` : ''),expression:\"versionStore.is_update_available ?\\n `アップデートがあります (version ${versionStore.latest_version})` : ''\",modifiers:{\"top\":true}}],staticClass:\"navigation__link\",class:{\n 'navigation__link--version': _vm.versionStore.is_client_develop_version,\n 'navigation__link--highlight': _vm.versionStore.is_update_available,\n },attrs:{\"active-class\":\"navigation__link--active\",\"href\":\"https://github.com/tsukumijima/KonomiTV\"}},[_c('Icon',{staticClass:\"navigation__link-icon\",attrs:{\"icon\":\"fluent:info-16-regular\",\"width\":\"26px\"}}),_c('span',{staticClass:\"navigation__link-text\"},[_vm._v(\"version \"+_vm._s(_vm.versionStore.client_version))])],1)],1)])]),_c('BottomNavigation')],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('v-bottom-navigation',{staticClass:\"bottom-navigation-container elevation-12\",attrs:{\"color\":\"primary\",\"grow\":\"\"}},[_c('v-btn',{staticClass:\"bottom-navigation-button\",attrs:{\"to\":\"/tv/\"}},[_c('span',{staticClass:\"mt-1\"},[_vm._v(\"テレビをみる\")]),_c('Icon',{attrs:{\"icon\":\"fluent:tv-20-regular\",\"width\":\"30px\"}})],1),_c('v-btn',{staticClass:\"bottom-navigation-button\",attrs:{\"to\":\"/videos/\"}},[_c('span',{staticClass:\"mt-1\"},[_vm._v(\"ビデオをみる\")]),_c('Icon',{attrs:{\"icon\":\"fluent:movies-and-tv-20-regular\",\"width\":\"30px\"}})],1),_c('v-btn',{staticClass:\"bottom-navigation-button\",attrs:{\"to\":\"/settings/\"}},[_c('span',{staticClass:\"mt-1\"},[_vm._v(\"設定\")]),_c('Icon',{attrs:{\"icon\":\"fluent:settings-20-regular\",\"width\":\"30px\"}})],1)],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","import { render, staticRenderFns } from \"./BottomNavigation.vue?vue&type=template&id=3df53df3&scoped=true&\"\nvar script = {}\nimport style0 from \"./BottomNavigation.vue?vue&type=style&index=0&id=3df53df3&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"3df53df3\",\n null\n \n)\n\nexport default component.exports","\nimport APIClient from '@/services/APIClient';\n\n\n/** バージョン情報を表すインターフェイス */\nexport interface IVersionInformation {\n version: string;\n latest_version: string;\n environment: 'Windows' | 'Linux' | 'Linux-Docker' | 'Linux-ARM';\n backend: 'EDCB' | 'Mirakurun';\n encoder: 'FFmpeg' | 'QSVEncC' | 'NVEncC' | 'VCEEncC' | 'rkmppenc';\n}\n\n\nclass Version {\n\n /**\n * バージョン情報を取得する\n * @returns バージョン情報 or バージョン情報の取得に失敗した場合は null\n */\n static async fetchServerVersion(): Promise {\n\n // API リクエストを実行\n const response = await APIClient.get('/version');\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'バージョン情報を取得できませんでした。');\n return null;\n }\n\n return response.data;\n }\n}\n\nexport default Version;\n","\nimport { defineStore } from 'pinia';\n\nimport Version, { IVersionInformation } from '@/services/Version';\nimport Utils from '@/utils';\n\n\n/**\n * 現在ログイン中のユーザーアカウントの情報を共有するストア\n */\nconst useVersionStore = defineStore('version', {\n state: () => ({\n\n // サーバーのバージョン情報\n server_version_info: null as IVersionInformation | null,\n\n // 最終更新日時 (UNIX タイムスタンプ、秒単位)\n last_updated_at: 0,\n }),\n getters: {\n client_version(): string {\n return Utils.version;\n },\n server_version(): string | null {\n return this.server_version_info?.version ?? null;\n },\n latest_version(): string | null {\n return this.server_version_info?.latest_version ?? null;\n },\n is_client_develop_version(): boolean {\n return this.client_version.includes('-dev');\n },\n is_server_develop_version(): boolean {\n return this.server_version?.includes('-dev') ?? false;\n },\n is_update_available(): boolean {\n // もし現在のサーバーバージョンと最新のバージョンが異なるなら、アップデートが利用できると判断する\n // 現在のサーバーバージョンが開発版 (-dev あり) で、かつ最新のバージョンがリリース版 (-dev なし) の場合も同様に表示する\n // つまり開発版だと同じバージョンのリリース版がリリースされたときにしかアップデート通知が表示されない事になるが、ひとまずこれで…\n if (this.server_version === null || this.latest_version === null) return false;\n if ((this.is_server_develop_version === false && this.server_version !== this.latest_version) ||\n (this.is_server_develop_version === true && this.server_version.replace('-dev', '') === this.latest_version)) {\n return true;\n }\n return false;\n },\n is_version_mismatch(): boolean {\n if (this.server_version === null) return false;\n return this.client_version !== this.server_version;\n }\n },\n actions: {\n\n /**\n * バージョン情報を取得する\n * すでに取得済みの情報がある場合は API リクエストを行わずにそれを返す\n * @param force 強制的に API リクエストを行う場合は true\n * @returns バージョン情報 or バージョン情報の取得に失敗した場合は null\n */\n async fetchServerVersion(force: boolean = false): Promise {\n\n // バージョン情報がある場合はそれを返す\n // force が true の場合は無視される\n if (this.server_version_info !== null && force === false) {\n // ただし、最終更新日時が1分以上前の場合は非同期で更新する\n if (Utils.time() - this.last_updated_at > 60) {\n this.fetchServerVersion(true);\n }\n return this.server_version_info;\n }\n\n // サーバーのバージョン情報を取得する\n const version_info = await Version.fetchServerVersion();\n if (version_info === null) {\n return null;\n }\n this.server_version_info = version_info;\n this.last_updated_at = Utils.time();\n\n return this.server_version_info;\n },\n }\n});\n\nexport default useVersionStore;\n","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport BottomNavigation from '@/components/BottomNavigation.vue';\nimport useVersionStore from '@/store/VersionStore';\n\nexport default Vue.extend({\n name: 'Navigation',\n components: {\n BottomNavigation,\n },\n computed: {\n ...mapStores(useVersionStore),\n },\n async created() {\n await this.versionStore.fetchServerVersion();\n }\n});\n\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Navigation.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Navigation.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Navigation.vue?vue&type=template&id=5b40940b&scoped=true&\"\nimport script from \"./Navigation.vue?vue&type=script&lang=ts&\"\nexport * from \"./Navigation.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Navigation.vue?vue&type=style&index=0&id=5b40940b&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"5b40940b\",\n null\n \n)\n\nexport default component.exports","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport Header from '@/components/Header.vue';\nimport Navigation from '@/components/Navigation.vue';\nimport useUserStore from '@/store/UserStore';\nimport Utils from '@/utils';\n\nexport default Vue.extend({\n name: 'Login',\n components: {\n Header,\n Navigation,\n },\n data() {\n return {\n\n // フォームを小さくするかどうか\n is_form_dense: Utils.isSmartphoneHorizontal(),\n\n username: '' as string,\n password: '' as string,\n password_showing: false,\n };\n },\n computed: {\n // UserStore に this.userStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useUserStore),\n },\n async created() {\n\n // アカウント情報を更新\n await this.userStore.fetchUser();\n\n // 現在ログイン中の場合はアカウントページに遷移\n if (this.userStore.is_logged_in) {\n await this.$router.replace({path: '/settings/account'});\n }\n },\n methods: {\n async login() {\n\n // ユーザー名またはパスワードが空\n if (this.username === '' || this.password === '') {\n this.$message.error('ユーザー名またはパスワードが空です。');\n return;\n }\n\n // ログイン処理 (エラーハンドリング含む) を実行\n const result = await this.userStore.login(this.username, this.password);\n if (result === false) {\n return; // ログイン失敗\n }\n\n // アカウントページに遷移\n // ブラウザバックでログインページに戻れないようにする\n await this.$router.replace({path: '/settings/account'});\n }\n }\n});\n\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Login.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Login.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Login.vue?vue&type=template&id=851c3dec&scoped=true&\"\nimport script from \"./Login.vue?vue&type=script&lang=ts&\"\nexport * from \"./Login.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Login.vue?vue&type=style&index=0&id=851c3dec&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"851c3dec\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"route-container\"},[_c('Header'),_c('main',[_c('Navigation'),_vm._m(0)],1)],1)\n}\nvar staticRenderFns = [function (){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"d-flex justify-center align-center w-100\"},[_c('div',{staticClass:\"d-flex justify-center align-center flex-column\"},[_c('h1',[_vm._v(\"Not Found, or Under Development...\")]),_c('span',{staticClass:\"mt-4 text--text text--darken-1\"},[_vm._v(\"お探しのページは存在しないか、鋭意開発中です。\")])])])\n}]\n\nexport { render, staticRenderFns }","\n\nimport Vue from 'vue';\n\nimport Header from '@/components/Header.vue';\nimport Navigation from '@/components/Navigation.vue';\n\nexport default Vue.extend({\n name: 'NotFound',\n components: {\n Header,\n Navigation,\n },\n});\n\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./NotFound.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./NotFound.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./NotFound.vue?vue&type=template&id=1310cfee&scoped=true&\"\nimport script from \"./NotFound.vue?vue&type=script&lang=ts&\"\nexport * from \"./NotFound.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./NotFound.vue?vue&type=style&index=0&id=1310cfee&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"1310cfee\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"route-container\"},[_c('Header'),_c('main',[_c('Navigation'),_c('div',{staticClass:\"register-container-wrapper d-flex align-center w-100 mb-13\"},[_c('v-card',{staticClass:\"register-container px-10 pt-8 pb-11 mx-auto background lighten-1\",attrs:{\"elevation\":\"10\",\"width\":\"100%\",\"max-width\":\"450\"}},[_c('v-card-title',{staticClass:\"register__logo flex-column justify-center\"},[_c('v-img',{attrs:{\"max-width\":\"250\",\"src\":\"/assets/images/logo.svg\"}}),_c('h4',{staticClass:\"mt-10\"},[_vm._v(\"アカウントを作成\")])],1),_c('v-divider'),_c('v-form',{ref:\"register\",on:{\"submit\":function($event){$event.preventDefault();}}},[_c('v-text-field',{staticClass:\"mt-12\",attrs:{\"outlined\":\"\",\"placeholder\":\"ユーザー名\",\"autofocus\":\"\",\"dense\":_vm.is_form_dense,\"rules\":[_vm.username_validation]},model:{value:(_vm.username),callback:function ($$v) {_vm.username=$$v},expression:\"username\"}}),_c('v-text-field',{staticStyle:{\"margin-top\":\"2px\"},attrs:{\"outlined\":\"\",\"placeholder\":\"パスワード\",\"dense\":_vm.is_form_dense,\"type\":_vm.password_showing ? 'text' : 'password',\"append-icon\":_vm.password_showing ? 'mdi-eye' : 'mdi-eye-off',\"rules\":[_vm.password_validation]},on:{\"click:append\":function($event){_vm.password_showing = !_vm.password_showing}},model:{value:(_vm.password),callback:function ($$v) {_vm.password=$$v},expression:\"password\"}}),_c('v-btn',{staticClass:\"register-button mt-5\",attrs:{\"color\":\"secondary\",\"depressed\":\"\",\"width\":\"100%\",\"height\":\"56\"},on:{\"click\":function($event){return _vm.register()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:person-add-20-filled\",\"height\":\"24\"}}),_vm._v(\"アカウントを作成 \")],1)],1)],1)],1)],1)],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport Header from '@/components/Header.vue';\nimport Navigation from '@/components/Navigation.vue';\nimport useUserStore from '@/store/UserStore';\nimport Utils from '@/utils';\n\nexport default Vue.extend({\n name: 'Register',\n components: {\n Header,\n Navigation,\n },\n data() {\n return {\n\n // フォームを小さくするかどうか\n is_form_dense: Utils.isSmartphoneHorizontal(),\n\n username: null as string | null,\n username_validation: (value: string | null) => {\n if (value === '' || value === null) return 'ユーザー名を入力してください。';\n if (/^.{2,}$/.test(value) === false) return 'ユーザー名は2文字以上で入力してください。';\n return true;\n },\n password: null as string | null,\n password_showing: true, // アカウント作成時はデフォルトでパスワードを表示する\n password_validation: (value: string | null) => {\n if (value === '' || value === null) return 'パスワードを入力してください。';\n // 正規表現の参考: https://qiita.com/grrrr/items/0b35b5c1c98eebfa5128\n if (/^[a-zA-Z0-9!-/:-@¥[-`{-~]{4,}$/.test(value) === false) return 'パスワードは4文字以上の半角英数記号を入力してください。';\n return true;\n },\n };\n },\n computed: {\n // UserStore に this.userStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useUserStore),\n },\n async created() {\n\n // アカウント情報を更新\n await this.userStore.fetchUser();\n\n // 現在ログイン中の場合はアカウントページに遷移\n if (this.userStore.is_logged_in) {\n await this.$router.replace({path: '/settings/account'});\n }\n },\n methods: {\n async register() {\n\n // すべてのバリデーションが通過したときのみ\n // ref: https://qiita.com/Hijiri_Ishi/items/56cac99c8f3806a6fa24\n if ((this.$refs.register as any).validate() === false) return;\n if (this.username === null || this.password === null) return;\n\n // アカウント作成 & ログイン処理 (エラーハンドリング含む) を実行\n const result = await this.userStore.register(this.username, this.password);\n if (result === false) {\n return; // ログイン失敗\n }\n\n // アカウントページに遷移\n // ブラウザバックでアカウント作成画面に戻れないようにする\n await this.$router.replace({path: '/settings/account'});\n }\n }\n});\n\n","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Register.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js!../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Register.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Register.vue?vue&type=template&id=6533f3d0&scoped=true&\"\nimport script from \"./Register.vue?vue&type=script&lang=ts&\"\nexport * from \"./Register.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Register.vue?vue&type=style&index=0&id=6533f3d0&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"6533f3d0\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('SettingsBase',[_c('h2',{staticClass:\"settings__heading\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"settings__back-button\",attrs:{\"to\":\"/settings/\"}},[_c('Icon',{attrs:{\"icon\":\"fluent:arrow-left-12-filled\",\"width\":\"25px\"}})],1),_c('Icon',{attrs:{\"icon\":\"fluent:person-20-filled\",\"width\":\"25px\"}}),_c('span',{staticClass:\"ml-2\"},[_vm._v(\"アカウント\")])],1),_c('div',{staticClass:\"settings__content\",class:{'settings__content--loading': _vm.is_loading}},[(_vm.userStore.user === null)?_c('div',{staticClass:\"account\"},[_c('div',{staticClass:\"account-wrapper\"},[_c('img',{staticClass:\"account__icon\",attrs:{\"src\":\"/assets/images/account-icon-default.png\"}}),_c('div',{staticClass:\"account__info\"},[_c('div',{staticClass:\"account__info-name\"},[_c('span',{staticClass:\"account__info-name-text\"},[_vm._v(\"ログインしていません\")])]),_c('span',{staticClass:\"account__info-id\"},[_vm._v(\"Not logged in\")])])]),_c('v-btn',{staticClass:\"account__login ml-auto\",attrs:{\"color\":\"secondary\",\"width\":\"140\",\"height\":\"56\",\"depressed\":\"\",\"to\":\"/login/\"}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fa:sign-in\"}}),_vm._v(\"ログイン \")],1)],1):_vm._e(),(_vm.userStore.user !== null)?_c('div',{staticClass:\"account\"},[_c('div',{staticClass:\"account-wrapper\"},[_c('img',{staticClass:\"account__icon\",attrs:{\"src\":_vm.userStore.user_icon_url}}),_c('div',{staticClass:\"account__info\"},[_c('div',{staticClass:\"account__info-name\"},[_c('span',{staticClass:\"account__info-name-text\"},[_vm._v(_vm._s(_vm.userStore.user.name))]),(_vm.userStore.user.is_admin)?_c('span',{staticClass:\"account__info-admin\"},[_vm._v(\"管理者\")]):_vm._e()]),_c('span',{staticClass:\"account__info-id\"},[_vm._v(\"User ID: \"+_vm._s(_vm.userStore.user.id))])])]),_c('v-btn',{staticClass:\"account__login ml-auto\",attrs:{\"color\":\"secondary\",\"width\":\"140\",\"height\":\"56\",\"depressed\":\"\"},on:{\"click\":function($event){return _vm.userStore.logout()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fa:sign-out\"}}),_vm._v(\"ログアウト \")],1)],1):_vm._e(),(_vm.userStore.is_logged_in === false)?_c('div',{staticClass:\"account-register\"},[_c('div',{staticClass:\"account-register__heading\"},[_vm._v(\" KonomiTV アカウントにログインすると、\"),_c('br'),_vm._v(\"より便利な機能が使えます! \")]),_c('div',{staticClass:\"account-register__feature\"},[_c('div',{staticClass:\"account-feature\"},[_c('Icon',{staticClass:\"account-feature__icon\",attrs:{\"icon\":\"bi:chat-left-text-fill\"}}),_c('div',{staticClass:\"account-feature__info\"},[_c('span',{staticClass:\"account-feature__info-heading\"},[_vm._v(\"ニコニコ実況にコメントする\")]),_c('span',{staticClass:\"account-feature__info-text\"},[_vm._v(\"テレビを見ながらニコニコ実況にコメントできます。別途、ニコニコアカウントとの連携が必要です。\")])])],1),_c('div',{staticClass:\"account-feature\"},[_c('Icon',{staticClass:\"account-feature__icon\",attrs:{\"icon\":\"fa-brands:twitter\"}}),_c('div',{staticClass:\"account-feature__info\"},[_c('span',{staticClass:\"account-feature__info-heading\"},[_vm._v(\"Twitter 連携機能\")]),_c('span',{staticClass:\"account-feature__info-text\"},[_vm._v(\"テレビを見ながら Twitter にツイートしたり、検索したツイートをリアルタイムで表示できます。別途、Twitter アカウントとの連携が必要です。\")])])],1),_c('div',{staticClass:\"account-feature\"},[_c('Icon',{staticClass:\"account-feature__icon\",attrs:{\"icon\":\"fluent:arrow-sync-20-filled\"}}),_c('div',{staticClass:\"account-feature__info\"},[_c('span',{staticClass:\"account-feature__info-heading\"},[_vm._v(\"設定をデバイス間で同期\")]),_c('span',{staticClass:\"account-feature__info-text\"},[_vm._v(\"ピン留めしたチャンネルなど、ブラウザに保存されている各種設定をブラウザやデバイスをまたいで同期できます。\")])])],1),_c('div',{staticClass:\"account-feature\"},[_c('Icon',{staticClass:\"account-feature__icon\",attrs:{\"icon\":\"fa-solid:sliders-h\"}}),_c('div',{staticClass:\"account-feature__info\"},[_c('span',{staticClass:\"account-feature__info-heading\"},[_vm._v(\"環境設定をブラウザから変更\")]),_c('span',{staticClass:\"account-feature__info-text\"},[_vm._v(\"管理者権限があれば、環境設定をブラウザから変更できます。一番最初に作成されたアカウントには、自動で管理者権限が付与されます。\")])])],1)]),_c('div',{staticClass:\"account-register__description\"},[_vm._v(\" KonomiTV アカウントの作成に必要なものは\"),_c('br',{staticClass:\"smartphone-vertical-only\"}),_vm._v(\"ユーザー名とパスワードだけです。\"),_c('br'),_vm._v(\" アカウントはローカルにインストールした\"),_c('br',{staticClass:\"smartphone-vertical-only\"}),_vm._v(\" KonomiTV サーバーごとに保存されます。\"),_c('br'),_vm._v(\" 外部のサービスには保存されませんので、ご安心ください。\"),_c('br')]),_c('v-btn',{staticClass:\"account-register__button\",attrs:{\"color\":\"secondary\",\"width\":\"100%\",\"max-width\":\"250\",\"height\":\"50\",\"depressed\":\"\",\"to\":\"/register/\"}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:person-add-20-filled\",\"height\":\"24\"}}),_vm._v(\"アカウントを作成 \")],1)],1):_vm._e(),(_vm.userStore.is_logged_in === true)?_c('div',[_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"sync_settings\"}},[_vm._v(\"設定をデバイス間で同期する\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"sync_settings\"}},[_vm._v(\" KonomiTV では、設定を同じアカウントでログインしているデバイス間で同期できます!\"),_c('br'),_vm._v(\" 同期をオンにすると、同期をオンにしているすべてのデバイスで共通の設定が使えます。ピン留めチャンネルやハッシュタグリストなども同期されます。\"),_c('br'),_vm._v(\" なお、デバイス固有の設定(画質設定など)は、同期後も各デバイスで個別に反映されます。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"sync_settings\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.sync_settings),callback:function ($$v) {_vm.sync_settings=$$v},expression:\"sync_settings\"}})],1),_c('v-dialog',{attrs:{\"max-width\":\"530\"},model:{value:(_vm.sync_settings_dialog),callback:function ($$v) {_vm.sync_settings_dialog=$$v},expression:\"sync_settings_dialog\"}},[_c('v-card',[_c('v-card-title',{staticClass:\"justify-center\"},[_vm._v(\"設定データの競合\")]),_c('v-card-text',[_vm._v(\" このデバイスの設定と、サーバーに保存されている設定が競合しています。\"),_c('br'),_vm._v(\" 一度上書きすると、元に戻すことはできません。慎重に選択してください。\"),_c('br')]),_c('div',{staticClass:\"d-flex flex-column px-4 pb-4 settings__conflict-dialog\"},[_c('v-btn',{staticClass:\"settings__save-button error--text text--lighten-1\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.overrideServerSettingsFromClient()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:document-arrow-up-16-filled\",\"height\":\"22px\"}}),_vm._v(\" サーバーに保存されている設定を、\"),_c('br',{staticClass:\"smartphone-vertical-only\"}),_vm._v(\"このデバイスの設定で上書きする \")],1),_c('v-btn',{staticClass:\"settings__save-button error--text text--lighten-1 mt-3\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.overrideClientSettingsFromServer()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:document-arrow-down-16-filled\",\"height\":\"22px\"}}),_vm._v(\" このデバイスの設定を、\"),_c('br',{staticClass:\"smartphone-vertical-only\"}),_vm._v(\"サーバーに保存されている設定で上書きする \")],1),_c('v-btn',{staticClass:\"settings__save-button mt-3\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){_vm.sync_settings_dialog = false}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:dismiss-16-filled\",\"height\":\"22px\"}}),_vm._v(\" キャンセル \")],1)],1)],1)],1),_c('v-form',{ref:\"settings_username\",staticClass:\"settings__item\",on:{\"submit\":function($event){$event.preventDefault();}}},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"ユーザー名\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" KonomiTV アカウントのユーザー名を設定します。アルファベットだけでなく日本語や記号も使えます。\"),_c('br'),_vm._v(\" 同じ KonomiTV サーバー上の他のアカウントと同じユーザー名には変更できません。\"),_c('br')]),_c('v-text-field',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"placeholder\":\"ユーザー名\",\"dense\":_vm.is_form_dense,\"rules\":[_vm.settings_username_validation]},model:{value:(_vm.settings_username),callback:function ($$v) {_vm.settings_username=$$v},expression:\"settings_username\"}})],1),_c('v-btn',{staticClass:\"settings__save-button\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.updateAccountInfo('username')}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:save-16-filled\",\"height\":\"24px\"}}),_vm._v(\"ユーザー名を更新 \")],1),_c('v-form',{staticClass:\"settings__item\",on:{\"submit\":function($event){$event.preventDefault();}}},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"アイコン画像\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" KonomiTV アカウントのアイコン画像を設定します。\"),_c('br'),_vm._v(\" アップロードされた画像は自動で 400×400 の正方形にリサイズされます。\"),_c('br')]),_c('v-file-input',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"placeholder\":\"アイコン画像を選択\",\"dense\":_vm.is_form_dense,\"accept\":\"image/jpeg, image/png\",\"prepend-icon\":\"\",\"prepend-inner-icon\":\"mdi-paperclip\"},model:{value:(_vm.settings_icon),callback:function ($$v) {_vm.settings_icon=$$v},expression:\"settings_icon\"}})],1),_c('v-btn',{staticClass:\"settings__save-button mt-5\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.updateAccountIcon()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:save-16-filled\",\"height\":\"24px\"}}),_vm._v(\"アイコン画像を更新 \")],1),_c('v-form',{ref:\"settings_password\",staticClass:\"settings__item\",on:{\"submit\":function($event){$event.preventDefault();}}},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"新しいパスワード\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" KonomiTV アカウントの新しいパスワードを設定します。\"),_c('br')]),_c('v-text-field',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"placeholder\":\"新しいパスワード\",\"dense\":_vm.is_form_dense,\"type\":_vm.settings_password_showing ? 'text' : 'password',\"append-icon\":_vm.settings_password_showing ? 'mdi-eye' : 'mdi-eye-off',\"rules\":[_vm.settings_password_validation]},on:{\"click:append\":function($event){_vm.settings_password_showing = !_vm.settings_password_showing}},model:{value:(_vm.settings_password),callback:function ($$v) {_vm.settings_password=$$v},expression:\"settings_password\"}})],1),_c('v-btn',{staticClass:\"settings__save-button\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.updateAccountInfo('password')}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:save-16-filled\",\"height\":\"24px\"}}),_vm._v(\"パスワードを更新 \")],1),_c('v-divider',{staticClass:\"mt-6\"}),_c('div',{staticClass:\"settings__item mt-6\"},[_c('div',{staticClass:\"settings__item-heading error--text text--lighten-1\"},[_vm._v(\"アカウントを削除\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" 現在ログインしている KonomiTV アカウントを削除します。\"),_c('br'),_c('b',[_vm._v(\"アカウントに紐づくすべてのデータが削除されます。\")]),_vm._v(\"元に戻すことはできません。\"),_c('br')])]),_c('v-dialog',{attrs:{\"max-width\":\"385\"},scopedSlots:_vm._u([{key:\"activator\",fn:function({ on }){return [_c('v-btn',_vm._g({staticClass:\"settings__save-button error mt-5\",attrs:{\"depressed\":\"\"}},on),[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:delete-16-filled\",\"height\":\"24px\"}}),_vm._v(\"アカウントを削除 \")],1)]}}],null,false,1849668703),model:{value:(_vm.account_delete_confirm_dialog),callback:function ($$v) {_vm.account_delete_confirm_dialog=$$v},expression:\"account_delete_confirm_dialog\"}},[_c('v-card',[_c('v-card-title',{staticClass:\"justify-center pt-6 font-weight-bold\"},[_vm._v(\"本当にアカウントを削除しますか?\")]),_c('v-card-text',{staticClass:\"pt-2 pb-0\"},[_vm._v(\" アカウントに紐づくすべてのデータが削除されます。元に戻すことはできません。\"),_c('br'),_vm._v(\" 本当にアカウントを削除しますか? \")]),_c('v-card-actions',{staticClass:\"pt-4 px-6 pb-5\"},[_c('v-spacer'),_c('v-btn',{attrs:{\"color\":\"text\",\"text\":\"\"},on:{\"click\":function($event){_vm.account_delete_confirm_dialog = false}}},[_vm._v(\"キャンセル\")]),_c('v-btn',{attrs:{\"color\":\"error\"},on:{\"click\":function($event){return _vm.deleteAccount()}}},[_vm._v(\"削除\")])],1)],1)],1)],1):_vm._e()])])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"route-container\"},[_c('Header'),_c('main',[_c('Navigation'),_c('v-card',{staticClass:\"settings-container d-flex px-5 py-5 mx-auto background\",attrs:{\"elevation\":\"0\",\"width\":\"100%\",\"max-width\":\"1000\"}},[_c('div',[_c('v-navigation-drawer',{staticClass:\"settings-navigation flex-shrink-0 background\",attrs:{\"permanent\":\"\",\"width\":\"195\",\"height\":\"auto\"}},[_c('v-list-item',{staticClass:\"px-4\"},[_c('v-list-item-content',[_c('h1',[_vm._v(\"設定\")])])],1),_c('v-list',{staticClass:\"mt-2 px-0\",attrs:{\"nav\":\"\"}},[_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/general\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{staticStyle:{\"padding\":\"0 3px\"},attrs:{\"icon\":\"fa-solid:sliders-h\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"全般\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/caption\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:subtitles-16-filled\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"字幕\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/capture\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:image-multiple-16-filled\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"キャプチャ\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/account\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:person-20-filled\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"アカウント\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/jikkyo\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{staticStyle:{\"padding\":\"0 2px\"},attrs:{\"icon\":\"bi:chat-left-text-fill\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"ニコニコ実況\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/twitter\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{staticStyle:{\"padding\":\"0 1px\"},attrs:{\"icon\":\"fa-brands:twitter\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"Twitter\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/server\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:server-surface-16-filled\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"サーバー設定\")])],1)],1)],1)],1)],1),_c('v-card',{staticClass:\"settings ml-5 px-7 py-7 lighten-1\",attrs:{\"width\":\"100%\"}},[_vm._t(\"default\")],2)],1)],1)],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport Vue from 'vue';\n\nimport Header from '@/components/Header.vue';\nimport Navigation from '@/components/Navigation.vue';\n\n// 設定のベース画面なので、ロジックは基本置かない\nexport default Vue.extend({\n name: 'Settings-Base',\n components: {\n Header,\n Navigation,\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Base.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Base.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Base.vue?vue&type=template&id=d0f5a998&scoped=true&\"\nimport script from \"./Base.vue?vue&type=script&lang=ts&\"\nexport * from \"./Base.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Base.vue?vue&type=style&index=0&id=d0f5a998&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"d0f5a998\",\n null\n \n)\n\nexport default component.exports","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport Settings from '@/services/Settings';\nimport useSettingsStore from '@/store/SettingsStore';\nimport useUserStore from '@/store/UserStore';\nimport Utils from '@/utils';\nimport SettingsBase from '@/views/Settings/Base.vue';\n\nexport default Vue.extend({\n name: 'Settings-Account',\n components: {\n SettingsBase,\n },\n data() {\n return {\n\n // フォームを小さくするかどうか\n is_form_dense: Utils.isSmartphoneHorizontal(),\n\n // ローディング中かどうか\n is_loading: true,\n\n // ユーザー名とパスワード\n // ログイン画面やアカウント作成画面の data と同一のもの\n settings_username: null as string | null,\n settings_username_validation: (value: string | null) => {\n if (value === '' || value === null) return 'ユーザー名を入力してください。';\n if (/^.{2,}$/.test(value) === false) return 'ユーザー名は2文字以上で入力してください。';\n return true;\n },\n settings_password: null as string | null,\n settings_password_showing: false,\n settings_password_validation: (value: string | null) => {\n if (value === '' || value === null) return 'パスワードを入力してください。';\n // 正規表現の参考: https://qiita.com/grrrr/items/0b35b5c1c98eebfa5128\n if (/^[a-zA-Z0-9!-/:-@¥[-`{-~]{4,}$/.test(value) === false) return 'パスワードは4文字以上の半角英数記号を入力してください。';\n return true;\n },\n\n // アップロードするアイコン画像\n settings_icon: null as File | null,\n\n // アカウント削除確認ダイヤログ\n account_delete_confirm_dialog: false,\n\n // 設定を同期するかの設定値\n sync_settings: useSettingsStore().settings.sync_settings as boolean,\n\n // 設定を同期するときのダイヤログ\n sync_settings_dialog: false,\n };\n },\n computed: {\n // SettingsStore / UserStore に this.settingsStore / this.userStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useSettingsStore, useUserStore),\n },\n async created() {\n\n // アカウント情報を更新\n await this.userStore.fetchUser();\n\n // ローディング状態を解除\n this.is_loading = false;\n },\n watch: {\n // sync_settings の値の変更を監視する\n async sync_settings() {\n\n // 同期がオンになった & ダイヤログが表示されていない\n if (this.sync_settings === true && this.sync_settings_dialog === false) {\n\n // 同期対象の設定キーのみで設定データをまとめ直す\n const sync_settings = this.settingsStore.getSyncableClientSettings();\n\n // 同期対象のこのクライアントの設定を再度 JSON にする(文字列比較のため)\n const sync_settings_json = JSON.stringify(sync_settings);\n\n // サーバーから設定データをダウンロード\n // 一度オブジェクトに戻したものをを再度 JSON にする(文字列比較のため)\n const server_sync_settings = await Settings.fetchClientSettings();\n if (server_sync_settings === null) {\n this.$message.error('サーバーから設定データを取得できませんでした。');\n return;\n }\n const server_sync_settings_json = JSON.stringify(server_sync_settings);\n\n // このクライアントの設定とサーバーに保存されている設定が一致しない(=競合している)\n if (sync_settings_json !== server_sync_settings_json) {\n\n // 一度同期のスイッチをオフにして、クライアントとサーバーどちらの設定を使うのかを選択させるダイヤログを表示\n this.sync_settings_dialog = true;\n this.sync_settings = false;\n\n // このクライアントの設定とサーバーに保存されている設定が一致する\n } else {\n\n // 特に設定の同期をオンにしても問題ないので、そのまま有効にする\n this.settingsStore.settings.sync_settings = true;\n }\n\n // 同期がオフになった & ダイヤログが表示されていない\n } else if (this.sync_settings === false && this.sync_settings_dialog === false) {\n this.settingsStore.settings.sync_settings = false;\n }\n }\n },\n methods: {\n\n // このクライアントの設定でサーバー上の設定を上書きする\n async overrideServerSettingsFromClient() {\n\n // 強制的にこのクライアントの設定をサーバーに同期\n await this.settingsStore.syncClientSettingsToServer(true);\n\n // 設定の同期を有効化\n this.settingsStore.settings.sync_settings = true;\n this.sync_settings = true;\n\n // ダイヤログを閉じる\n this.sync_settings_dialog = false;\n },\n\n // サーバー上の設定でこのクライアントの設定を上書きする\n async overrideClientSettingsFromServer() {\n\n // 強制的にサーバーに保存されている設定データをこのクライアントに同期する\n // 設定の同期を有効化する前に実行しておくのが重要\n await this.settingsStore.syncClientSettingsFromServer(true);\n\n // 設定の同期を有効化\n // 値を変更した時点で設定データがサーバーにアップロードされてしまうので、\n // それよりも前に syncClientSettingsFromServer(true) でサーバー上の設定データを同期させておく必要がある\n // さもなければ、サーバー上の設定データがこのクライアントの設定で上書きされてしまい、overrideServerSettingsFromClient() と同じ挙動になってしまう\n this.settingsStore.settings.sync_settings = true;\n this.sync_settings = true;\n\n // ダイヤログを閉じる\n this.sync_settings_dialog = false;\n },\n\n async updateAccountInfo(update_type: 'username' | 'password') {\n\n // すべてのバリデーションが通過したときのみ\n // ref: https://qiita.com/Hijiri_Ishi/items/56cac99c8f3806a6fa24\n if (update_type === 'username') {\n if ((this.$refs.settings_username as any).validate() === false) return;\n } else {\n if ((this.$refs.settings_password as any).validate() === false) return;\n }\n\n // アカウント情報の更新処理 (エラーハンドリングを含む) を実行\n if (update_type === 'username') {\n if (this.settings_username === null) return;\n await this.userStore.updateUser({username: this.settings_username});\n } else {\n if (this.settings_password === null) return;\n await this.userStore.updateUser({password: this.settings_password});\n }\n },\n\n async updateAccountIcon() {\n\n // アイコン画像が選択されていないなら更新しない\n if (this.settings_icon === null) {\n this.$message.error('アップロードする画像を選択してください!');\n return;\n }\n\n // アイコン画像の更新処理 (エラーハンドリングを含む) を実行\n await this.userStore.updateUserIcon(this.settings_icon);\n },\n\n async deleteAccount() {\n\n // ダイヤログを閉じる\n this.account_delete_confirm_dialog = false;\n\n // アカウント削除処理 (エラーハンドリングを含む) を実行\n await this.userStore.deleteUser();\n }\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Account.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Account.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Account.vue?vue&type=template&id=7749b102&scoped=true&\"\nimport script from \"./Account.vue?vue&type=script&lang=ts&\"\nexport * from \"./Account.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Account.vue?vue&type=style&index=0&id=7749b102&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"7749b102\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('SettingsBase',[_c('h2',{staticClass:\"settings__heading\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"settings__back-button\",attrs:{\"to\":\"/settings/\"}},[_c('Icon',{attrs:{\"icon\":\"fluent:arrow-left-12-filled\",\"width\":\"25px\"}})],1),_c('Icon',{attrs:{\"icon\":\"fluent:subtitles-16-filled\",\"width\":\"25px\"}}),_c('span',{staticClass:\"ml-3\"},[_vm._v(\"字幕\")])],1),_c('div',{staticClass:\"settings__content\"},[_c('div',{staticClass:\"settings__item\"},[_c('label',{staticClass:\"settings__item-heading\"},[_vm._v(\"字幕のフォント\")]),_c('label',{staticClass:\"settings__item-label\"},[_vm._v(\" プレイヤーで字幕表示をオンにしているときの、字幕のフォントを設定します。\"),_c('br')]),_c('v-select',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"items\":_vm.caption_font},model:{value:(_vm.settingsStore.settings.caption_font),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"caption_font\", $$v)},expression:\"settingsStore.settings.caption_font\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"always_border_caption_text\"}},[_vm._v(\"字幕の文字を常に縁取りする\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"always_border_caption_text\"}},[_vm._v(\" 字幕表示時、縁取りをオンにすると、字幕が見やすくきれいになります。とくに理由がなければ、オンにしておくのがおすすめです。\"),_c('br'),_vm._v(\" この設定がオフのときも、字幕データ側で縁取りが指定されていれば、オンのときと同様に縁取り付きで描画されます。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"always_border_caption_text\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.always_border_caption_text),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"always_border_caption_text\", $$v)},expression:\"settingsStore.settings.always_border_caption_text\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"specify_caption_opacity\"}},[_vm._v(\"字幕の不透明度を指定する\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"specify_caption_opacity\"}},[_vm._v(\" 字幕表示時、不透明度を自分で指定するか設定できます。\"),_c('br'),_vm._v(\" この設定がオフのときは、字幕データ側で指定されている不透明度で描画します。とくに理由がなければ、オフにしておくのがおすすめです。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"specify_caption_opacity\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.specify_caption_opacity),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"specify_caption_opacity\", $$v)},expression:\"settingsStore.settings.specify_caption_opacity\"}})],1),_c('div',{staticClass:\"settings__item\",class:{'settings__item--disabled': _vm.settingsStore.settings.specify_caption_opacity === false}},[_c('label',{staticClass:\"settings__item-heading\"},[_vm._v(\"字幕の不透明度\")]),_c('label',{staticClass:\"settings__item-label\"},[_vm._v(\" 上の [字幕の不透明度を指定する] をオンに設定したときのみ有効です。不透明度を 0 に設定すれば、字幕の背景を非表示にできます。\"),_c('br')]),_c('div',{ref:\"caption_opacity\",staticClass:\"settings__item-label\"},[_c('v-slider',{staticClass:\"settings__item-form\",attrs:{\"ticks\":\"always\",\"thumb-label\":\"\",\"hide-details\":\"\",\"min\":0,\"max\":1,\"step\":0.05,\"disabled\":_vm.settingsStore.settings.specify_caption_opacity === false},model:{value:(_vm.settingsStore.settings.caption_opacity),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"caption_opacity\", $$v)},expression:\"settingsStore.settings.caption_opacity\"}})],1)]),_c('v-divider',{staticClass:\"mt-6\"}),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"tv_show_superimpose\"}},[_vm._v(\"テレビをみるときに文字スーパーを表示する\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"tv_show_superimpose\"}},[_vm._v(\" 文字スーパーは、緊急地震速報の赤テロップや、NHK BS のニュース速報のテロップなどで利用されています。とくに理由がなければ、オンにしておくのがおすすめです。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"tv_show_superimpose\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.tv_show_superimpose),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"tv_show_superimpose\", $$v)},expression:\"settingsStore.settings.tv_show_superimpose\"}})],1)],1)])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport useSettingsStore from '@/store/SettingsStore';\nimport Utils from '@/utils';\nimport SettingsBase from '@/views/Settings/Base.vue';\n\nexport default Vue.extend({\n name: 'Settings-Caption',\n components: {\n SettingsBase,\n },\n data() {\n return {\n\n // フォームを小さくするかどうか\n is_form_dense: Utils.isSmartphoneHorizontal(),\n\n // 字幕のフォントの選択肢\n caption_font: [\n {text: 'Windows TV ゴシック', value: 'Windows TV Gothic'},\n {text: 'Windows TV 丸ゴシック', value: 'Windows TV MaruGothic'},\n {text: 'Windows TV 太丸ゴシック', value: 'Windows TV FutoMaruGothic'},\n {text: 'ヒラギノTV丸ゴ', value: 'Hiragino TV Sans Rd S'},\n {text: '新丸ゴ ARIB', value: 'TT-ShinMGo-regular'},\n {text: 'Rounded M+ 1m for ARIB', value: 'Rounded M+ 1m for ARIB'},\n {text: 'Noto Sans JP', value: 'Noto Sans JP Caption'},\n {text: 'デフォルトのフォント', value: 'sans-serif'},\n ],\n };\n },\n computed: {\n // SettingsStore に this.settingsStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useSettingsStore),\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Caption.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Caption.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Caption.vue?vue&type=template&id=0642ebd7&\"\nimport script from \"./Caption.vue?vue&type=script&lang=ts&\"\nexport * from \"./Caption.vue?vue&type=script&lang=ts&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('SettingsBase',[_c('h2',{staticClass:\"settings__heading\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"settings__back-button\",attrs:{\"to\":\"/settings/\"}},[_c('Icon',{attrs:{\"icon\":\"fluent:arrow-left-12-filled\",\"width\":\"25px\"}})],1),_c('Icon',{attrs:{\"icon\":\"fluent:image-multiple-16-filled\",\"width\":\"26px\"}}),_c('span',{staticClass:\"ml-2\"},[_vm._v(\"キャプチャ\")])],1),_c('div',{staticClass:\"settings__content\"},[_c('div',{staticClass:\"settings__item settings__item--switch settings__item--sync-disabled\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"capture_copy_to_clipboard\"}},[_vm._v(\"キャプチャをクリップボードにコピーする\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"capture_copy_to_clipboard\"}},[_vm._v(\" この設定をオンにすると、撮ったキャプチャ画像がクリップボードにもコピーされます。\"),_c('br'),_vm._v(\" クリップボードの履歴をサポートしていない OS では、この設定をオンにしてキャプチャを撮ると、以前のクリップボードが上書きされます。注意してください。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"capture_copy_to_clipboard\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.capture_copy_to_clipboard),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"capture_copy_to_clipboard\", $$v)},expression:\"settingsStore.settings.capture_copy_to_clipboard\"}})],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"キャプチャの保存先\")]),_c('div',{staticClass:\"settings__item-label\"},[_c('p',[_vm._v(\" キャプチャした画像をブラウザでダウンロードするか、KonomiTV サーバーにアップロードするかを設定します。\"),_c('br'),_vm._v(\" ブラウザでのダウンロードと、KonomiTV サーバーへのアップロードを両方同時に行うこともできます。\"),_c('br')]),_c('p',[_vm._v(\" ブラウザでダウンロードすると、視聴中のデバイスのダウンロードフォルダに保存されます。\"),_c('br'),_vm._v(\" 視聴中のデバイスにそのまま保存されるためシンプルですが、保存先のフォルダを変更できないこと、PC 版 Chrome では毎回ダウンロードバーが表示されてしまったり、iOS Safari (PWA モード) ではダウンロードするとファイル概要画面が表示され再生が中断してしまったりなど、視聴に支障することがデメリットです (将来的には、iOS / Android アプリ版や拡張機能などで解消される予定) 。\"),_c('br')]),_c('p',[_vm._v(\" KonomiTV サーバーにアップロードすると、環境設定で指定されたキャプチャ保存フォルダに保存されます。視聴したデバイスにかかわらず、今までに撮ったキャプチャをひとつのフォルダにまとめて保存できます。\"),_c('br'),_vm._v(\" 他のデバイスでキャプチャを見るにはキャプチャ保存フォルダをネットワークに共有する必要があること、スマホ・タブレットではネットワーク上のフォルダへのアクセスがやや面倒なことがデメリットです。(将来的には、保存フォルダ内のキャプチャを Google フォトのように表示する機能を追加予定)\"),_c('br')])]),_c('v-select',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"items\":_vm.capture_save_mode},model:{value:(_vm.settingsStore.settings.capture_save_mode),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"capture_save_mode\", $$v)},expression:\"settingsStore.settings.capture_save_mode\"}})],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"字幕表示時のキャプチャの保存モード\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" 字幕表示時、キャプチャした画像に字幕を合成するかを設定します。\"),_c('br'),_vm._v(\" 映像のみのキャプチャと、字幕を合成したキャプチャを両方同時に保存することもできます。\"),_c('br'),_vm._v(\" なお、字幕非表示時は、常に映像のみ (+コメント付きキャプチャではコメントを合成して) 保存されます。\"),_c('br')]),_c('v-select',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"items\":_vm.capture_caption_mode},model:{value:(_vm.settingsStore.settings.capture_caption_mode),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"capture_caption_mode\", $$v)},expression:\"settingsStore.settings.capture_caption_mode\"}})],1)])])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport useSettingsStore from '@/store/SettingsStore';\nimport Utils from '@/utils';\nimport SettingsBase from '@/views/Settings/Base.vue';\n\nexport default Vue.extend({\n name: 'Settings-Capture',\n components: {\n SettingsBase,\n },\n data() {\n return {\n\n // フォームを小さくするかどうか\n is_form_dense: Utils.isSmartphoneHorizontal(),\n\n // キャプチャの保存先の選択肢\n capture_save_mode: [\n {text: 'ブラウザでダウンロード', value: 'Browser'},\n {text: 'KonomiTV サーバーにアップロード', value: 'UploadServer'},\n {text: 'ブラウザでのダウンロードと、KonomiTV サーバーへのアップロードを両方行う', value: 'Both'},\n ],\n\n // 字幕が表示されているときのキャプチャの保存モードの選択肢\n capture_caption_mode: [\n {text: '映像のみのキャプチャを保存する', value: 'VideoOnly'},\n {text: '字幕を合成したキャプチャを保存する', value: 'CompositingCaption'},\n {text: '映像のみのキャプチャと、字幕を合成したキャプチャを両方保存する', value: 'Both'},\n ],\n };\n },\n computed: {\n // SettingsStore に this.settingsStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useSettingsStore),\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Capture.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Capture.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Capture.vue?vue&type=template&id=053733a0&\"\nimport script from \"./Capture.vue?vue&type=script&lang=ts&\"\nexport * from \"./Capture.vue?vue&type=script&lang=ts&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('SettingsBase',[_c('h2',{staticClass:\"settings__heading\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"settings__back-button\",attrs:{\"to\":\"/settings/\"}},[_c('Icon',{attrs:{\"icon\":\"fluent:arrow-left-12-filled\",\"width\":\"25px\"}})],1),_c('Icon',{attrs:{\"icon\":\"fa-solid:sliders-h\",\"width\":\"19px\"}}),_c('span',{staticClass:\"ml-3\"},[_vm._v(\"全般\")])],1),_c('div',{staticClass:\"settings__content\"},[_c('div',{staticClass:\"settings__item settings__item--sync-disabled\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"テレビのデフォルトのストリーミング画質\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" テレビをライブストリーミングするときのデフォルトの画質を設定します。\"),_c('br'),_vm._v(\" ストリーミング画質はプレイヤーの設定からいつでも切り替えられます。\"),_c('br')]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" [1080p (60fps)] は、通常 30fps (60i) の映像を補間し、より滑らか(ぬるぬる)な映像で視聴できます!\"),_c('br'),_vm._v(\" [1080p (60fps)] で視聴するときは、環境設定の [利用するエンコーダー] をハードウェアエンコーダーに設定してください。FFmpeg (ソフトウェアエンコーダー) では、再生に支障が出ることがあります。\"),_c('br')]),_c('v-select',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"items\":_vm.tv_streaming_quality},model:{value:(_vm.settingsStore.settings.tv_streaming_quality),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"tv_streaming_quality\", $$v)},expression:\"settingsStore.settings.tv_streaming_quality\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch settings__item--sync-disabled\",class:{'settings__item--disabled': _vm.PlayerUtils.isHEVCVideoSupported() === false}},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"tv_data_saver_mode\"}},[_vm._v(\"テレビを通信節約モードで視聴する\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"tv_data_saver_mode\"}},[_vm._v(\" 通信節約モードでは、H.265 / HEVC という圧縮率の高いコーデックを使い、画質はほぼそのまま、通信量を通常の 1/2 程度に抑えながら視聴できます!\"),_c('br'),_vm._v(\" 通信節約モードで視聴するときは、環境設定の [利用するエンコーダー] をハードウェアエンコーダーに設定してください。FFmpeg (ソフトウェアエンコーダー) では、再生に支障が出る可能性が高いです。\"),_c('br'),(_vm.PlayerUtils.isHEVCVideoSupported() === false && _vm.Utils.isFirefox() === false)?_c('p',{staticClass:\"mt-1 mb-0 error--text lighten-1\"},[_vm._v(\" このデバイスでは通信節約モードがサポートされていません。 \")]):_vm._e(),(_vm.PlayerUtils.isHEVCVideoSupported() === false && _vm.Utils.isFirefox() === true)?_c('p',{staticClass:\"mt-1 mb-0 error--text lighten-1\"},[_vm._v(\" お使いの Firefox ブラウザでは通信節約モードがサポートされていません。 \")]):_vm._e()]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"tv_data_saver_mode\",\"inset\":\"\",\"hide-details\":\"\",\"disabled\":_vm.PlayerUtils.isHEVCVideoSupported() === false},model:{value:(_vm.settingsStore.settings.tv_data_saver_mode),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"tv_data_saver_mode\", $$v)},expression:\"settingsStore.settings.tv_data_saver_mode\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch settings__item--sync-disabled\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"tv_low_latency_mode\"}},[_vm._v(\"テレビを低遅延で視聴する\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"tv_low_latency_mode\"}},[_vm._v(\" 低遅延ストリーミングをオンにすると、\"),_c('b',[_vm._v(\"放送波との遅延を最短 0.9 秒に抑えて視聴できます!\")]),_c('br'),_vm._v(\" また、約 3 秒以上遅延したときに少しだけ再生速度を早める (1.1x) ことで、滑らかにストリーミングの遅延を取り戻します。\"),_c('br'),_vm._v(\" 宅外視聴などのネットワークが不安定になりがちな環境では、低遅延ストリーミングをオフにしてみると、映像のカクつきを改善できるかもしれません。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"tv_low_latency_mode\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.tv_low_latency_mode),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"tv_low_latency_mode\", $$v)},expression:\"settingsStore.settings.tv_low_latency_mode\"}})],1),_c('v-divider',{staticClass:\"mt-6\"}),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"デフォルトのパネルの表示状態\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" 視聴画面を開いたときに、右側のパネルをどう表示するかを設定します。\"),_c('br')]),_c('v-select',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"items\":_vm.panel_display_state},model:{value:(_vm.settingsStore.settings.panel_display_state),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"panel_display_state\", $$v)},expression:\"settingsStore.settings.panel_display_state\"}})],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"テレビをみるときにデフォルトで表示されるパネルのタブ\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" テレビの視聴画面を開いたときに、右側のパネルで最初に表示されるタブを設定します。\"),_c('br')]),_c('v-select',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"items\":_vm.tv_panel_active_tab},model:{value:(_vm.settingsStore.settings.tv_panel_active_tab),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"tv_panel_active_tab\", $$v)},expression:\"settingsStore.settings.tv_panel_active_tab\"}})],1),_c('v-divider',{staticClass:\"mt-6\"}),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"tv_show_superimpose\"}},[_vm._v(\"チャンネル選局のキーボードショートカットを \"+_vm._s(_vm.Utils.AltOrOption())+\" + 数字キー/テンキーに変更する\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"tv_show_superimpose\"}},[_vm._v(\" この設定をオンにすると、数字キーまたはテンキーに対応するリモコン番号(1~12)のチャンネルに切り替える際、\"+_vm._s(_vm.Utils.AltOrOption())+\" キーを同時に押す必要があります。\"),_c('br'),_vm._v(\" コメントやツイートを入力しようとして誤って数字キーを押してしまい、チャンネルが変わってしまう事態を避けたい方におすすめです。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"tv_show_superimpose\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.tv_channel_selection_requires_alt_key),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"tv_channel_selection_requires_alt_key\", $$v)},expression:\"settingsStore.settings.tv_channel_selection_requires_alt_key\"}})],1),_c('v-divider',{staticClass:\"mt-6\"}),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"設定をエクスポート\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" このデバイス(ブラウザ)に保存されている設定データを、エクスポート(ダウンロード)できます。\"),_c('br'),_vm._v(\" ダウンロードした設定データ (KonomiTV-Settings.json) は、[設定をインポート] からインポートできます。異なるサーバーの KonomiTV を同じ設定で使いたいときなどに使ってください。\"),_c('br')])]),_c('v-btn',{staticClass:\"settings__save-button mt-4\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.exportSettings()}}},[_c('Icon',{staticClass:\"mr-3\",attrs:{\"icon\":\"fa6-solid:download\",\"height\":\"19px\"}}),_vm._v(\"設定をエクスポート \")],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading error--text text--lighten-1\"},[_vm._v(\"設定をインポート\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" [設定をエクスポート] でダウンロードした設定データを、このデバイス(ブラウザ)にインポートできます。\"),_c('br'),_vm._v(\" 設定をインポートすると、\"),_c('b',[_vm._v(\"現在のデバイス設定はすべて上書きされます。\")]),_vm._v(\"元に戻すことはできません。\"),_c('br'),_vm._v(\" 設定のデバイス間同期がオンのときは、\"),_c('b',[_vm._v(\"同期が有効なすべてのデバイスに反映されます。\")]),_vm._v(\"十分ご注意ください。\"),_c('br')]),_c('v-file-input',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"placeholder\":\"設定データ (KonomiTV-Settings.json) を選択\",\"dense\":_vm.is_form_dense,\"accept\":\"application/json\",\"prepend-icon\":\"\",\"prepend-inner-icon\":\"mdi-paperclip\"},model:{value:(_vm.import_settings_file),callback:function ($$v) {_vm.import_settings_file=$$v},expression:\"import_settings_file\"}})],1),_c('v-btn',{staticClass:\"settings__save-button error mt-5\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.importSettings()}}},[_c('Icon',{staticClass:\"mr-3\",attrs:{\"icon\":\"fa6-solid:upload\",\"height\":\"19px\"}}),_vm._v(\"設定をインポート \")],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading error--text text--lighten-1\"},[_vm._v(\"設定を初期状態にリセット\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" このデバイス(ブラウザ)に保存されている設定データを、初期状態のデフォルト値にリセットできます。\"),_c('br'),_vm._v(\" 設定をリセットすると、元に戻すことはできません。\"),_c('br'),_vm._v(\" 設定のデバイス間同期がオンのときは、\"),_c('b',[_vm._v(\"同期が有効なすべてのデバイスに反映されます。\")]),_vm._v(\"十分ご注意ください。\"),_c('br')])]),_c('v-btn',{staticClass:\"settings__save-button error mt-5\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.resetSettings()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"material-symbols:device-reset-rounded\",\"height\":\"23px\"}}),_vm._v(\"設定をリセット \")],1)],1)])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport useSettingsStore from '@/store/SettingsStore';\nimport Utils, { PlayerUtils } from '@/utils';\nimport SettingsBase from '@/views/Settings/Base.vue';\n\nconst QUALITY_H264 = [\n {text: '1080p (60fps) (約4.50GB/h / 平均10.0Mbps)', value: '1080p-60fps'},\n {text: '1080p (約4.50GB/h / 平均10.0Mbps)', value: '1080p'},\n {text: '810p (約2.62GB/h / 平均5.8Mbps)', value: '810p'},\n {text: '720p (約2.18GB/h / 平均4.9Mbps)', value: '720p'},\n {text: '540p (約1.52GB/h / 平均3.4Mbps)', value: '540p'},\n {text: '480p (約1.06GB/h / 平均2.3Mbps)', value: '480p'},\n {text: '360p (約0.60GB/h / 平均1.3Mbps)', value: '360p'},\n {text: '240p (約0.35GB/h / 平均0.8Mbps)', value: '240p'},\n];\n\nconst QUALITY_H265 = [\n {text: '1080p (60fps) (約1.80GB/h / 平均4.0Mbps)', value: '1080p-60fps'},\n {text: '1080p (約1.37GB/h / 平均3.0Mbps)', value: '1080p'},\n {text: '810p (約1.05GB/h / 平均2.3Mbps)', value: '810p'},\n {text: '720p (約0.82GB/h / 平均1.8Mbps)', value: '720p'},\n {text: '540p (約0.53GB/h / 平均1.2Mbps)', value: '540p'},\n {text: '480p (約0.46GB/h / 平均1.0Mbps)', value: '480p'},\n {text: '360p (約0.30GB/h / 平均0.7Mbps)', value: '360p'},\n {text: '240p (約0.20GB/h / 平均0.4Mbps)', value: '240p'},\n];\n\nexport default Vue.extend({\n name: 'Settings-General',\n components: {\n SettingsBase,\n },\n data() {\n return {\n\n // ユーティリティをテンプレートで使えるように\n Utils: Utils,\n PlayerUtils: PlayerUtils,\n\n // フォームを小さくするかどうか\n is_form_dense: Utils.isSmartphoneHorizontal(),\n\n // テレビのデフォルトのストリーミング画質の選択肢\n tv_streaming_quality: QUALITY_H264,\n\n // デフォルトのパネルの表示状態の選択肢\n panel_display_state: [\n {text: '前回の状態を復元する', value: 'RestorePreviousState'},\n {text: '常に表示する', value: 'AlwaysDisplay'},\n {text: '常に折りたたむ', value: 'AlwaysFold'},\n ],\n\n // テレビをみるときにデフォルトで表示されるパネルのタブの選択肢\n tv_panel_active_tab: [\n {text: '番組情報タブ', value: 'Program'},\n {text: 'チャンネルタブ', value: 'Channel'},\n {text: 'コメントタブ', value: 'Comment'},\n {text: 'Twitter タブ', value: 'Twitter'},\n ],\n\n // 選択された設定データ (KonomiTV-Settings.json) が入る\n import_settings_file: null as File | null,\n };\n },\n computed: {\n // SettingsStore に this.settingsStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useSettingsStore),\n },\n created() {\n if (this.settingsStore.settings.tv_data_saver_mode === true) {\n this.tv_streaming_quality = QUALITY_H265;\n }\n },\n watch: {\n 'settingsStore.settings.tv_data_saver_mode': {\n immediate: true,\n handler(val: boolean) {\n if (val === true) {\n this.tv_streaming_quality = QUALITY_H265;\n } else {\n this.tv_streaming_quality = QUALITY_H264;\n }\n },\n }\n },\n methods: {\n\n // 設定データをエクスポートする\n exportSettings() {\n\n // 設定データを JSON 化して取得\n const settings_json = JSON.stringify(this.settingsStore.settings, null, 4);\n\n // ダウンロードさせるために一旦 Blob にしてから、KonomiTV-Settings.json としてダウンロード\n const settings_json_blob = new Blob([settings_json], {type: 'application/json'});\n Utils.downloadBlobData(settings_json_blob, 'KonomiTV-Settings.json');\n this.$message.success('設定をエクスポートしました。');\n },\n\n // 設定データをインポートする\n async importSettings() {\n\n // 設定データが選択されていないときは実行しない\n if (this.import_settings_file === null) {\n this.$message.error('インポートする設定データを選択してください!');\n return;\n }\n\n // 設定データのインポートを実行\n const result = await this.settingsStore.importClientSettings(this.import_settings_file);\n if (result === true) {\n this.$message.success('設定をインポートしました。');\n window.setTimeout(() => this.$router.go(0), 300);\n } else {\n this.$message.error('設定データが不正なため、インポートできませんでした。');\n }\n },\n\n // 設定データをリセットする\n async resetSettings() {\n await this.settingsStore.resetClientSettings();\n this.$message.success('設定をリセットしました。');\n window.setTimeout(() => this.$router.go(0), 300);\n },\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./General.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./General.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./General.vue?vue&type=template&id=24ae53a3&\"\nimport script from \"./General.vue?vue&type=script&lang=ts&\"\nexport * from \"./General.vue?vue&type=script&lang=ts&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"route-container\"},[_c('Header'),_c('main',[_c('Navigation'),_c('v-card',{staticClass:\"settings-container d-flex px-5 py-5 mx-auto background\",attrs:{\"elevation\":\"0\",\"width\":\"100%\",\"max-width\":\"1000\"}},[_c('v-navigation-drawer',{staticClass:\"settings-navigation flex-shrink-0 background\",attrs:{\"permanent\":\"\",\"width\":\"100%\",\"height\":\"auto\"}},[_c('v-list-item',{staticClass:\"px-1\"},[_c('v-list-item-content',[_c('h1',[_vm._v(\"設定\")])])],1),_c('v-list',{staticClass:\"mt-2 px-0\",attrs:{\"nav\":\"\"}},[_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/general\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{staticStyle:{\"padding\":\"0 3px\"},attrs:{\"icon\":\"fa-solid:sliders-h\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"全般\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/caption\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:subtitles-16-filled\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"字幕\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/capture\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:image-multiple-16-filled\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"キャプチャ\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/account\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:person-20-filled\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"アカウント\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/jikkyo\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{staticStyle:{\"padding\":\"0 2px\"},attrs:{\"icon\":\"bi:chat-left-text-fill\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"ニコニコ実況\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/twitter\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{staticStyle:{\"padding\":\"0 1px\"},attrs:{\"icon\":\"fa-brands:twitter\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"Twitter\")])],1)],1),_c('v-list-item',{staticClass:\"px-4\",attrs:{\"link\":\"\",\"color\":\"primary\",\"to\":\"/settings/server\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:server-surface-16-filled\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\"サーバー設定\")])],1)],1),_c('v-list-item',{staticClass:\"px-4 settings-navigation-version\",class:{'settings-navigation-version--highlight': _vm.versionStore.is_update_available},attrs:{\"link\":\"\",\"color\":\"primary\",\"href\":\"https://github.com/tsukumijima/KonomiTV\"}},[_c('v-list-item-icon',{staticClass:\"mr-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:info-16-regular\",\"width\":\"26px\"}})],1),_c('v-list-item-content',[_c('v-list-item-title',[_vm._v(\" version \"+_vm._s(_vm.versionStore.client_version)+_vm._s(_vm.versionStore.is_update_available ? ' (Update Available)' : '')+\" \")])],1)],1)],1)],1)],1)],1)],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport Header from '@/components/Header.vue';\nimport Navigation from '@/components/Navigation.vue';\nimport useVersionStore from '@/store/VersionStore';\n\nexport default Vue.extend({\n name: 'Settings-Index',\n components: {\n Header,\n Navigation,\n },\n computed: {\n ...mapStores(useVersionStore),\n },\n async created() {\n await this.versionStore.fetchServerVersion();\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Index.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Index.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Index.vue?vue&type=template&id=48d089f3&scoped=true&\"\nimport script from \"./Index.vue?vue&type=script&lang=ts&\"\nexport * from \"./Index.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Index.vue?vue&type=style&index=0&id=48d089f3&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"48d089f3\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('SettingsBase',[_c('h2',{staticClass:\"settings__heading\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"settings__back-button\",attrs:{\"to\":\"/settings/\"}},[_c('Icon',{attrs:{\"icon\":\"fluent:arrow-left-12-filled\",\"width\":\"25px\"}})],1),_c('Icon',{attrs:{\"icon\":\"bi:chat-left-text-fill\",\"width\":\"19px\"}}),_c('span',{staticClass:\"ml-3\"},[_vm._v(\"ニコニコ実況\")])],1),_c('div',{staticClass:\"settings__content\",class:{'settings__content--loading': _vm.is_loading}},[(_vm.userStore.user === null || _vm.userStore.user.niconico_user_id === null)?_c('div',{staticClass:\"niconico-account niconico-account--anonymous\"},[_c('div',{staticClass:\"niconico-account-wrapper\"},[_c('Icon',{staticClass:\"flex-shrink-0\",attrs:{\"icon\":\"bi:chat-left-text-fill\",\"width\":\"45px\"}}),_c('div',{staticClass:\"niconico-account__info ml-4\"},[_c('div',{staticClass:\"niconico-account__info-name\"},[_c('span',{staticClass:\"niconico-account__info-name-text\"},[_vm._v(\"ニコニコアカウントと連携していません\")])]),_c('span',{staticClass:\"niconico-account__info-description\"},[_vm._v(\" ニコニコアカウントと連携すると、テレビを見ながらニコニコ実況にコメントできるようになります。 \")])])],1),_c('v-btn',{staticClass:\"niconico-account__login ml-auto\",attrs:{\"color\":\"secondary\",\"width\":\"130\",\"height\":\"56\",\"depressed\":\"\"},on:{\"click\":function($event){return _vm.loginNiconicoAccount()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:plug-connected-20-filled\",\"height\":\"26\"}}),_vm._v(\"連携する \")],1)],1):_vm._e(),(_vm.userStore.user !== null && _vm.userStore.user.niconico_user_id !== null)?_c('div',{staticClass:\"niconico-account\"},[_c('div',{staticClass:\"niconico-account-wrapper\"},[_c('img',{staticClass:\"niconico-account__icon\",attrs:{\"src\":_vm.userStore.user_niconico_icon_url}}),_c('div',{staticClass:\"niconico-account__info\"},[_c('div',{staticClass:\"niconico-account__info-name\"},[_c('span',{staticClass:\"niconico-account__info-name-text\"},[_vm._v(_vm._s(_vm.userStore.user.niconico_user_name)+\" と連携しています\")])]),_c('span',{staticClass:\"niconico-account__info-description\"},[_c('span',{staticClass:\"mr-2\",staticStyle:{\"white-space\":\"nowrap\"}},[_vm._v(\"Niconico User ID:\")]),_c('a',{staticClass:\"mr-2\",attrs:{\"href\":`https://www.nicovideo.jp/user/${_vm.userStore.user.niconico_user_id}`,\"target\":\"_blank\"}},[_vm._v(_vm._s(_vm.userStore.user.niconico_user_id))]),(_vm.userStore.user.niconico_user_premium == true)?_c('span',{staticClass:\"secondary--text\"},[_vm._v(\"(Premium)\")]):_vm._e()])])]),_c('v-btn',{staticClass:\"niconico-account__login ml-auto\",attrs:{\"color\":\"secondary\",\"width\":\"130\",\"height\":\"56\",\"depressed\":\"\"},on:{\"click\":function($event){return _vm.logoutNiconicoAccount()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:plug-disconnected-20-filled\",\"height\":\"26\"}}),_vm._v(\"連携解除 \")],1)],1):_vm._e(),_c('div',{staticClass:\"settings__item mt-7\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"コメントのミュート設定\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" 表示したくないコメントを、映像上やコメントリストに表示しないようにミュートできます。\"),_c('br')])]),_c('v-btn',{staticClass:\"settings__save-button mt-4\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){_vm.comment_mute_settings_modal = !_vm.comment_mute_settings_modal}}},[_c('Icon',{attrs:{\"icon\":\"heroicons-solid:filter\",\"height\":\"19px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(\"コメントのミュート設定を開く\")])],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"コメントの速さ\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" プレイヤーに流れるコメントの速さを設定します。\"),_c('br'),_vm._v(\" たとえば 1.2 に設定すると、コメントが 1.2 倍速く流れます。\"),_c('br')]),_c('v-slider',{staticClass:\"settings__item-form\",attrs:{\"ticks\":\"always\",\"thumb-label\":\"\",\"hide-details\":\"\",\"step\":0.1,\"min\":0.5,\"max\":2},model:{value:(_vm.settingsStore.settings.comment_speed_rate),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"comment_speed_rate\", $$v)},expression:\"settingsStore.settings.comment_speed_rate\"}})],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"コメントの文字サイズ\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" プレイヤーに流れるコメントの文字サイズの基準値を設定します。\"),_c('br'),_vm._v(\" 実際の文字サイズは画面サイズに合わせて調整されます。デフォルトの文字サイズは 34px です。\"),_c('br')]),_c('v-slider',{staticClass:\"settings__item-form\",attrs:{\"ticks\":\"always\",\"thumb-label\":\"\",\"hide-details\":\"\",\"min\":20,\"max\":60},model:{value:(_vm.settingsStore.settings.comment_font_size),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"comment_font_size\", $$v)},expression:\"settingsStore.settings.comment_font_size\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"close_comment_form_after_sending\"}},[_vm._v(\"コメント送信後にコメント入力フォームを閉じる\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"close_comment_form_after_sending\"}},[_vm._v(\" この設定をオンにすると、コメントを送信した後に、コメント入力フォームが自動で閉じるようになります。\"),_c('br'),_vm._v(\" コメント入力フォームが表示されたままだと、大半のショートカットキーが文字入力と競合して使えなくなります。とくに理由がなければ、オンにしておくのがおすすめです。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"close_comment_form_after_sending\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.close_comment_form_after_sending),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"close_comment_form_after_sending\", $$v)},expression:\"settingsStore.settings.close_comment_form_after_sending\"}})],1)],1),_c('CommentMuteSettings',{model:{value:(_vm.comment_mute_settings_modal),callback:function ($$v) {_vm.comment_mute_settings_modal=$$v},expression:\"comment_mute_settings_modal\"}})],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('v-dialog',{attrs:{\"max-width\":\"770\",\"transition\":\"slide-y-transition\"},model:{value:(_vm.comment_mute_settings_modal),callback:function ($$v) {_vm.comment_mute_settings_modal=$$v},expression:\"comment_mute_settings_modal\"}},[_c('v-card',{staticClass:\"comment-mute-settings\"},[_c('v-card-title',{staticClass:\"px-5 pt-5 pb-3 d-flex align-center font-weight-bold\",staticStyle:{\"height\":\"60px\"}},[_c('Icon',{attrs:{\"icon\":\"heroicons-solid:filter\",\"height\":\"26px\"}}),_c('span',{staticClass:\"ml-3\"},[_vm._v(\"コメントのミュート設定\")]),_c('v-spacer'),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"d-flex align-center rounded-circle cursor-pointer px-2 py-2\",on:{\"click\":function($event){_vm.comment_mute_settings_modal = false}}},[_c('Icon',{attrs:{\"icon\":\"fluent:dismiss-12-filled\",\"width\":\"23px\",\"height\":\"23px\"}})],1)],1),_c('div',{staticClass:\"px-5 pb-5\"},[_c('div',{staticClass:\"text-subtitle-1 d-flex align-center font-weight-bold mt-4\"},[_c('Icon',{attrs:{\"icon\":\"fa-solid:sliders-h\",\"width\":\"24px\",\"height\":\"20px\"}}),_c('span',{staticClass:\"ml-2\"},[_vm._v(\"クイック設定\")])],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"mute_vulgar_comments\"}},[_vm._v(\" 露骨な表現を含むコメントをミュートする \")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"mute_vulgar_comments\"}},[_vm._v(\" 性的な単語などの露骨・下品な表現を含むコメントを、一括でミュートするかを設定します。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"mute_vulgar_comments\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.mute_vulgar_comments),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"mute_vulgar_comments\", $$v)},expression:\"settingsStore.settings.mute_vulgar_comments\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"mute_abusive_discriminatory_prejudiced_comments\"}},[_vm._v(\" ネガティブな表現、差別的な表現、政治的に偏った表現を含むコメントをミュートする \")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"mute_abusive_discriminatory_prejudiced_comments\"}},[_vm._v(\" 『死ね』『殺す』などのネガティブな表現、特定の国や人々への差別的な表現、政治的に偏った表現を含むコメントを、一括でミュートするかを設定します。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"mute_abusive_discriminatory_prejudiced_comments\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.mute_abusive_discriminatory_prejudiced_comments),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"mute_abusive_discriminatory_prejudiced_comments\", $$v)},expression:\"settingsStore.settings.mute_abusive_discriminatory_prejudiced_comments\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"mute_big_size_comments\"}},[_vm._v(\" 文字サイズが大きいコメントをミュートする \")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"mute_big_size_comments\"}},[_vm._v(\" 通常より大きい文字サイズで表示されるコメントを、一括でミュートするかを設定します。\"),_c('br'),_vm._v(\" 文字サイズが大きいコメントには迷惑なコメントが多いです。基本的にはオンにしておくのがおすすめです。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"mute_big_size_comments\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.mute_big_size_comments),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"mute_big_size_comments\", $$v)},expression:\"settingsStore.settings.mute_big_size_comments\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"mute_fixed_comments\"}},[_vm._v(\" 映像の上下に固定表示されるコメントをミュートする \")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"mute_fixed_comments\"}},[_vm._v(\" 映像の上下に固定された状態で表示されるコメントを、一括でミュートするかを設定します。\"),_c('br'),_vm._v(\" 固定表示されるコメントが煩わしい方におすすめです。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"mute_fixed_comments\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.mute_fixed_comments),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"mute_fixed_comments\", $$v)},expression:\"settingsStore.settings.mute_fixed_comments\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"mute_colored_comments\"}},[_vm._v(\" 色付きのコメントをミュートする \")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"mute_colored_comments\"}},[_vm._v(\" 白以外の色で表示される色付きのコメントを、一括でミュートするかを設定します。\"),_c('br'),_vm._v(\" この設定をオンにしておくと、目立つ色のコメントを一掃できます。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"mute_colored_comments\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.mute_colored_comments),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"mute_colored_comments\", $$v)},expression:\"settingsStore.settings.mute_colored_comments\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"mute_consecutive_same_characters_comments\"}},[_vm._v(\" 8文字以上同じ文字が連続しているコメントをミュートする \")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"mute_consecutive_same_characters_comments\"}},[_vm._v(\" 『wwwwwwwwwww』『あばばばばばばばばば』など、8文字以上同じ文字が連続しているコメントを、一括でミュートするかを設定します。\"),_c('br'),_vm._v(\" しばしばあるテンプレコメントが煩わしい方におすすめです。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"mute_consecutive_same_characters_comments\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.mute_consecutive_same_characters_comments),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"mute_consecutive_same_characters_comments\", $$v)},expression:\"settingsStore.settings.mute_consecutive_same_characters_comments\"}})],1),_c('div',{staticClass:\"text-subtitle-1 d-flex align-center font-weight-bold mt-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:comment-dismiss-20-filled\",\"width\":\"24px\"}}),_c('span',{staticClass:\"ml-2 mr-2\"},[_vm._v(\"ミュート済みのキーワード\")]),_c('v-btn',{staticClass:\"ml-auto\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.settingsStore.settings.muted_comment_keywords.push({match: 'partial', pattern: ''})}}},[_c('Icon',{attrs:{\"icon\":\"fluent:add-12-filled\",\"height\":\"17px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(\"追加\")])],1)],1),_c('div',{staticClass:\"muted-comment-items\"},_vm._l((_vm.settingsStore.settings.muted_comment_keywords),function(muted_comment_keyword,index){return _c('div',{key:index,staticClass:\"muted-comment-item\"},[_c('v-text-field',{staticClass:\"muted-comment-item__input\",attrs:{\"type\":\"search\",\"dense\":\"\",\"outlined\":\"\",\"hide-details\":\"\",\"placeholder\":\"ミュートするキーワードを入力\"},model:{value:(_vm.settingsStore.settings.muted_comment_keywords[index].pattern),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings.muted_comment_keywords[index], \"pattern\", $$v)},expression:\"settingsStore.settings.muted_comment_keywords[index].pattern\"}}),_c('v-select',{staticClass:\"muted-comment-item__match-type\",attrs:{\"dense\":\"\",\"outlined\":\"\",\"hide-details\":\"\",\"items\":_vm.muted_comment_keyword_match_type},model:{value:(_vm.settingsStore.settings.muted_comment_keywords[index].match),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings.muted_comment_keywords[index], \"match\", $$v)},expression:\"settingsStore.settings.muted_comment_keywords[index].match\"}}),_c('button',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"muted-comment-item__delete-button\",on:{\"click\":function($event){_vm.settingsStore.settings.muted_comment_keywords\n .splice(_vm.settingsStore.settings.muted_comment_keywords.indexOf(muted_comment_keyword), 1)}}},[_c('Icon',{attrs:{\"icon\":\"fluent:delete-16-filled\",\"width\":\"20px\"}})],1)],1)}),0),_c('div',{staticClass:\"text-subtitle-1 d-flex align-center font-weight-bold mt-4\"},[_c('Icon',{attrs:{\"icon\":\"fluent:person-prohibited-20-filled\",\"width\":\"24px\"}}),_c('span',{staticClass:\"ml-2 mr-2\"},[_vm._v(\"ミュート済みのニコニコユーザー ID\")]),_c('v-btn',{staticClass:\"ml-auto\",attrs:{\"depressed\":\"\"},on:{\"click\":function($event){return _vm.settingsStore.settings.muted_niconico_user_ids.push('')}}},[_c('Icon',{attrs:{\"icon\":\"fluent:add-12-filled\",\"height\":\"17px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(\"追加\")])],1)],1),_c('div',{staticClass:\"muted-comment-items\"},_vm._l((_vm.settingsStore.settings.muted_niconico_user_ids),function(muted_niconico_user_id,index){return _c('div',{key:index,staticClass:\"muted-comment-item\"},[_c('v-text-field',{staticClass:\"muted-comment-item__input\",attrs:{\"type\":\"search\",\"dense\":\"\",\"outlined\":\"\",\"hide-details\":\"\",\"placeholder\":\"ミュートするニコニコユーザー ID を入力\"},model:{value:(_vm.settingsStore.settings.muted_niconico_user_ids[index]),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings.muted_niconico_user_ids, index, $$v)},expression:\"settingsStore.settings.muted_niconico_user_ids[index]\"}}),_c('button',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"muted-comment-item__delete-button\",on:{\"click\":function($event){_vm.settingsStore.settings.muted_niconico_user_ids\n .splice(_vm.settingsStore.settings.muted_niconico_user_ids.indexOf(muted_niconico_user_id), 1)}}},[_c('Icon',{attrs:{\"icon\":\"fluent:delete-16-filled\",\"width\":\"20px\"}})],1)],1)}),0)])],1)],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport { mapStores } from 'pinia';\nimport Vue, { PropType } from 'vue';\n\nimport useSettingsStore from '@/store/SettingsStore';\n\nexport default Vue.extend({\n name: 'CommentMuteSettings',\n // カスタム v-model を実装する\n // ref: https://jp.vuejs.org/v2/guide/components-custom-events.html\n model: {\n prop: 'showing', // v-model で渡された値が \"showing\" props に入る\n event: 'change', // \"change\" イベントで親コンポーネントに反映\n },\n props: {\n // コメントのミュート設定のモーダルを表示するか\n showing: {\n type: Boolean as PropType,\n required: true,\n }\n },\n data() {\n return {\n\n // インターバルのタイマー ID\n interval_timer_id: 0,\n\n // コメントのミュート設定のモーダルを表示するか\n comment_mute_settings_modal: false,\n\n // ミュート済みのキーワードのマッチタイプ\n muted_comment_keyword_match_type: [\n {text: '部分一致', value: 'partial'},\n {text: '前方一致', value: 'forward'},\n {text: '後方一致', value: 'backward'},\n {text: '完全一致', value: 'exact'},\n {text: '正規表現', value: 'regex'},\n ],\n };\n },\n computed: {\n // SettingsStore に this.settingsStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useSettingsStore),\n },\n watch: {\n\n // showing (親コンポーネント側) の変更を監視し、変更されたら comment_mute_settings_modal に反映する\n showing() {\n this.comment_mute_settings_modal = this.showing as boolean;\n },\n\n // comment_mute_settings_modal (子コンポーネント側) の変更を監視し、変更されたら this.$emit() で親コンポーネントに伝える\n comment_mute_settings_modal() {\n this.$emit('change', this.comment_mute_settings_modal);\n }\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./CommentMuteSettings.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./CommentMuteSettings.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./CommentMuteSettings.vue?vue&type=template&id=2cd59ba0&scoped=true&\"\nimport script from \"./CommentMuteSettings.vue?vue&type=script&lang=ts&\"\nexport * from \"./CommentMuteSettings.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./CommentMuteSettings.vue?vue&type=style&index=0&id=2cd59ba0&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"2cd59ba0\",\n null\n \n)\n\nexport default component.exports","\nimport APIClient from '@/services/APIClient';\n\n\n/** ニコニコアカウントと連携するための認証 URL を表すインターフェイス */\nexport interface INiconicoAuthURL {\n authorization_url: string;\n}\n\n\nclass Niconico {\n\n /**\n * ニコニコアカウントと連携するための認証 URL を取得する\n * @returns 認証 URL or 認証 URL の取得に失敗した場合は null\n */\n static async fetchAuthorizationURL(): Promise {\n\n // API リクエストを実行\n const response = await APIClient.get('/niconico/auth');\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'ニコニコアカウントとの連携用の認証 URL を取得できませんでした。');\n return null;\n }\n\n return response.data.authorization_url;\n }\n\n\n /**\n * 現在ログイン中のユーザーアカウントに紐づくニコニコアカウントとの連携を解除する\n * @returns 連携解除に成功した場合は true, 失敗した場合は false\n */\n static async logoutAccount(): Promise {\n\n // API リクエストを実行\n const response = await APIClient.delete('/niconico/logout');\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'ニコニコアカウントとの連携を解除できませんでした。');\n return false;\n }\n\n return true;\n }\n}\n\nexport default Niconico;\n","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport CommentMuteSettings from '@/components/Settings/CommentMuteSettings.vue';\nimport Niconico from '@/services/Niconico';\nimport useSettingsStore from '@/store/SettingsStore';\nimport useUserStore from '@/store/UserStore';\nimport Utils from '@/utils';\nimport SettingsBase from '@/views/Settings/Base.vue';\n\nexport default Vue.extend({\n name: 'Settings-Jikkyo',\n components: {\n SettingsBase,\n CommentMuteSettings,\n },\n data() {\n return {\n\n // コメントのミュート設定のモーダルを表示するか\n comment_mute_settings_modal: false,\n\n // ローディング中かどうか\n is_loading: true,\n };\n },\n computed: {\n // SettingsStore / UserStore に this.settingsStore / this.userStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useSettingsStore, useUserStore),\n },\n async created() {\n\n // アカウント情報を更新\n await this.userStore.fetchUser();\n\n // ローディング状態を解除\n this.is_loading = false;\n\n // もしハッシュ (# から始まるフラグメント) に何か指定されていたら、\n // OAuth 連携のコールバックの結果が入っている可能性が高いので、パースを試みる\n // アカウント情報更新より後にしないと Snackbar がうまく表示されない\n if (location.hash !== '') {\n const params = new URLSearchParams(location.hash.replace('#', ''));\n if (params.get('status') !== null && params.get('detail') !== null) {\n // コールバックの結果を取得できたので、OAuth 連携の結果を画面に通知する\n const authorization_status = parseInt(params.get('status')!);\n const authorization_detail = params.get('detail')!;\n this.onOAuthCallbackReceived(authorization_status, authorization_detail);\n // URL からフラグメントを削除\n // ref: https://stackoverflow.com/a/49373716/17124142\n history.replaceState(null, '', ' ');\n }\n }\n },\n methods: {\n async loginNiconicoAccount() {\n\n // ログインしていない場合はエラーにする\n if (this.userStore.is_logged_in === false) {\n this.$message.warning('連携をはじめるには、KonomiTV アカウントにログインしてください。');\n return;\n }\n\n // ニコニコアカウントと連携するための認証 URL を取得\n const authorization_url = await Niconico.fetchAuthorizationURL();\n if (authorization_url === null) {\n return;\n }\n\n // モバイルデバイスではポップアップが事実上使えない (特に Safari ではブロックされてしまう) ので、素直にリダイレクトで実装する\n if (Utils.isMobileDevice() === true) {\n location.href = authorization_url;\n return;\n }\n\n // OAuth 連携のため、認証 URL をポップアップウインドウで開く\n // window.open() の第2引数はユニークなものにしておくと良いらしい\n // ref: https://qiita.com/catatsuy/items/babce8726ea78f5d25b1 (大変参考になりました)\n const popup_window = window.open(authorization_url, 'KonomiTV-OAuthPopup', Utils.getWindowFeatures());\n if (popup_window === null) {\n this.$message.error('ポップアップウインドウを開けませんでした。');\n return;\n }\n\n // 認証完了 or 失敗後、ポップアップウインドウから送信される文字列を受信\n const onMessage = async (event) => {\n\n // すでにウインドウが閉じている場合は実行しない\n if (popup_window.closed) return;\n\n // 受け取ったオブジェクトに KonomiTV-OAuthPopup キーがない or そもそもオブジェクトではない際は実行しない\n // ブラウザの拡張機能から結構余計な message が飛んでくるっぽい…。\n if (Utils.typeof(event.data) !== 'object') return;\n if (('KonomiTV-OAuthPopup' in event.data) === false) return;\n\n // 認証は完了したので、ポップアップウインドウを閉じ、リスナーを解除する\n if (popup_window) popup_window.close();\n window.removeEventListener('message', onMessage);\n\n // ステータスコードと詳細メッセージを取得\n const authorization_status = event.data['KonomiTV-OAuthPopup']['status'] as number;\n const authorization_detail = event.data['KonomiTV-OAuthPopup']['detail'] as string;\n this.onOAuthCallbackReceived(authorization_status, authorization_detail);\n };\n\n // postMessage() を受信するリスナーを登録\n window.addEventListener('message', onMessage);\n },\n\n async onOAuthCallbackReceived(authorization_status: number, authorization_detail: string) {\n console.log(`NiconicoAuthCallbackAPI: Status: ${authorization_status} / Detail: ${authorization_detail}`);\n\n // OAuth 連携に失敗した\n if (authorization_status !== 200) {\n if (authorization_detail.startsWith('Authorization was denied (access_denied)')) {\n this.$message.error('ニコニコアカウントとの連携がキャンセルされました。');\n } else if (authorization_detail.startsWith('Failed to get access token (HTTP Error ')) {\n const error = authorization_detail.replace('Failed to get access token ', '');\n this.$message.error(`アクセストークンの取得に失敗しました。${error}`);\n } else if (authorization_detail.startsWith('Failed to get access token (Connection Timeout)')) {\n this.$message.error('アクセストークンの取得に失敗しました。ニコニコで障害が発生している可能性があります。');\n } else if (authorization_detail.startsWith('Failed to get user information (HTTP Error ')) {\n const error = authorization_detail.replace('Failed to get user information ', '');\n this.$message.error(`ニコニコアカウントのユーザー情報の取得に失敗しました。${error}`);\n } else if (authorization_detail.startsWith('Failed to get user information (Connection Timeout)')) {\n this.$message.error('ニコニコアカウントのユーザー情報の取得に失敗しました。ニコニコで障害が発生している可能性があります。');\n } else {\n this.$message.error(`ニコニコアカウントとの連携に失敗しました。(${authorization_detail})`);\n }\n return;\n }\n\n // アカウント情報を強制的に更新\n await this.userStore.fetchUser(true);\n\n this.$message.success('ニコニコアカウントと連携しました。');\n },\n\n async logoutNiconicoAccount() {\n\n // ニコニコアカウント連携解除 API にリクエスト\n const result = await Niconico.logoutAccount();\n if (result === false) {\n return;\n }\n\n // アカウント情報を強制的に更新\n await this.userStore.fetchUser(true);\n\n this.$message.success('ニコニコアカウントとの連携を解除しました。');\n },\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Jikkyo.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Jikkyo.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Jikkyo.vue?vue&type=template&id=ac48731c&scoped=true&\"\nimport script from \"./Jikkyo.vue?vue&type=script&lang=ts&\"\nexport * from \"./Jikkyo.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Jikkyo.vue?vue&type=style&index=0&id=ac48731c&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"ac48731c\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('SettingsBase',[_c('h2',{staticClass:\"settings__heading\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"settings__back-button\",attrs:{\"to\":\"/settings/\"}},[_c('Icon',{attrs:{\"icon\":\"fluent:arrow-left-12-filled\",\"width\":\"25px\"}})],1),_c('Icon',{attrs:{\"icon\":\"fluent:server-surface-16-filled\",\"width\":\"22px\"}}),_c('span',{staticClass:\"ml-2\"},[_vm._v(\"サーバー設定\")])],1),_c('div',{staticClass:\"settings__content\"},[_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"鋭意開発中…\")])])])])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport Vue from 'vue';\n\nimport SettingsBase from '@/views/Settings/Base.vue';\n\nexport default Vue.extend({\n name: 'Settings-Server',\n components: {\n SettingsBase,\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Server.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Server.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Server.vue?vue&type=template&id=7cf86dbd&\"\nimport script from \"./Server.vue?vue&type=script&lang=ts&\"\nexport * from \"./Server.vue?vue&type=script&lang=ts&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('SettingsBase',[_c('h2',{staticClass:\"settings__heading\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"settings__back-button\",attrs:{\"to\":\"/settings/\"}},[_c('Icon',{attrs:{\"icon\":\"fluent:arrow-left-12-filled\",\"width\":\"25px\"}})],1),_c('Icon',{attrs:{\"icon\":\"fa-brands:twitter\",\"width\":\"22px\"}}),_c('span',{staticClass:\"ml-3\"},[_vm._v(\"Twitter\")])],1),_c('div',{staticClass:\"settings__content\",class:{'settings__content--loading': _vm.is_loading}},[_c('div',{staticClass:\"twitter-accounts\"},[(_vm.userStore.user !== null && _vm.userStore.user.twitter_accounts.length > 0)?_c('div',{staticClass:\"twitter-accounts__heading\"},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:person-board-20-filled\",\"height\":\"30\"}}),_vm._v(\"連携中のアカウント \")],1):_vm._e(),(_vm.userStore.user === null || _vm.userStore.user.twitter_accounts.length === 0)?_c('div',{staticClass:\"twitter-accounts__guide\"},[_c('Icon',{staticClass:\"flex-shrink-0\",attrs:{\"icon\":\"fa-brands:twitter\",\"width\":\"45px\"}}),_c('div',{staticClass:\"ml-4\"},[_c('div',{staticClass:\"font-weight-bold text-h6\"},[_vm._v(\"Twitter アカウントと連携していません\")]),_c('div',{staticClass:\"text--text text--darken-1 text-subtitle-2 mt-1\"},[_vm._v(\" Twitter アカウントと連携すると、テレビを見ながら Twitter にツイートしたり、ほかの実況ツイートをリアルタイムで表示できるようになります。 \")])])],1):_vm._e(),_vm._l(((_vm.userStore.user !== null ? _vm.userStore.user.twitter_accounts: [])),function(twitter_account){return _c('div',{key:twitter_account.id,staticClass:\"twitter-account\"},[_c('img',{staticClass:\"twitter-account__icon\",attrs:{\"src\":twitter_account.icon_url}}),_c('div',{staticClass:\"twitter-account__info\"},[_c('div',{staticClass:\"twitter-account__info-name\"},[_c('span',{staticClass:\"twitter-account__info-name-text\"},[_vm._v(_vm._s(twitter_account.name))])]),_c('span',{staticClass:\"twitter-account__info-screen-name\"},[_vm._v(\" @\"+_vm._s(twitter_account.screen_name)+\" \"),(twitter_account.is_oauth_session === true)?_c('span',[_vm._v(\"(Legacy Session)\")]):_vm._e()])]),_c('v-btn',{staticClass:\"twitter-account__logout ml-auto\",attrs:{\"width\":\"124\",\"height\":\"52\",\"depressed\":\"\"},on:{\"click\":function($event){return _vm.logoutTwitterAccount(twitter_account.screen_name)}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:plug-disconnected-20-filled\",\"height\":\"24\"}}),_vm._v(\"連携解除 \")],1)],1)}),_c('v-btn',{staticClass:\"twitter-account__login\",attrs:{\"color\":\"secondary\",\"max-width\":\"250\",\"height\":\"50\",\"depressed\":\"\"},on:{\"click\":function($event){return _vm.loginTwitterAccountWithPasswordForm()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:plug-connected-20-filled\",\"height\":\"24\"}}),_vm._v(\"連携するアカウントを追加 \")],1),_c('v-dialog',{attrs:{\"max-width\":\"600\"},model:{value:(_vm.twitter_password_auth_dialog),callback:function ($$v) {_vm.twitter_password_auth_dialog=$$v},expression:\"twitter_password_auth_dialog\"}},[_c('v-card',[_c('v-card-title',{staticClass:\"justify-center pt-6 font-weight-bold\"},[_vm._v(\"Twitter にログイン\")]),_c('v-card-text',{staticClass:\"pt-2 pb-0\"},[_c('p',{staticClass:\"mb-1\"},[_vm._v(\"2023/4/30 以降、Twitter のサードパーティー API の事実上の廃止により、従来のアプリ連携では Twitter にアクセスできなくなりました。\")]),_c('p',{staticClass:\"mb-1\"},[_vm._v(\"そこで KonomiTV では、代わりにユーザー名とパスワードでログインすることで、これまで通り Twitter 連携ができるようにしています (2要素認証を設定しているアカウントには対応していません) 。\")]),_c('p',{staticClass:\"mb-1\"},[_vm._v(\"万全は期していますが、非公式な方法のため、使い方次第ではアカウントにペナルティが適用される可能性もあります。自己の責任のもとでご利用ください。\")]),_c('v-form',{ref:\"twitter_form\",staticClass:\"settings__item\",on:{\"submit\":function($event){$event.preventDefault();}}},[_c('v-text-field',{ref:\"twitter_screen_name\",staticClass:\"settings__item-form mt-6\",attrs:{\"outlined\":\"\",\"label\":\"ユーザー名 (@ から始まる ID)\",\"placeholder\":\"screen_name\",\"dense\":_vm.is_form_dense,\"rules\":[(value) => !!value || 'ユーザー名を入力してください。']},model:{value:(_vm.twitter_screen_name),callback:function ($$v) {_vm.twitter_screen_name=$$v},expression:\"twitter_screen_name\"}}),_c('v-text-field',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"label\":\"パスワード\",\"dense\":_vm.is_form_dense,\"type\":_vm.twitter_password_showing ? 'text' : 'password',\"append-icon\":_vm.twitter_password_showing ? 'mdi-eye' : 'mdi-eye-off',\"rules\":[(value) => !!value || 'パスワードを入力してください。']},on:{\"click:append\":function($event){_vm.twitter_password_showing = !_vm.twitter_password_showing}},model:{value:(_vm.twitter_password),callback:function ($$v) {_vm.twitter_password=$$v},expression:\"twitter_password\"}})],1)],1),_c('v-card-actions',{staticClass:\"pt-0 px-6 pb-5\"},[_c('v-spacer'),_c('v-btn',{attrs:{\"color\":\"text\",\"height\":\"40\",\"text\":\"\"},on:{\"click\":function($event){_vm.twitter_password_auth_dialog = false}}},[_vm._v(\"キャンセル\")]),_c('v-btn',{staticClass:\"px-4\",attrs:{\"color\":\"secondary\",\"height\":\"40\"},on:{\"click\":function($event){return _vm.loginTwitterAccountWithPassword()}}},[_vm._v(\"ログイン\")])],1)],1)],1),_c('v-btn',{staticClass:\"twitter-account__login\",attrs:{\"color\":\"secondary\",\"max-width\":\"310\",\"height\":\"50\",\"depressed\":\"\"},on:{\"click\":function($event){return _vm.loginTwitterAccountWithOAuth()}}},[_c('Icon',{staticClass:\"mr-2\",attrs:{\"icon\":\"fluent:plug-connected-20-filled\",\"height\":\"24\"}}),_vm._v(\"連携するアカウントを追加 (Legacy) \")],1)],2),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"fold_panel_after_sending_tweet\"}},[_vm._v(\"ツイート送信後にパネルを折りたたむ\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"fold_panel_after_sending_tweet\"}},[_vm._v(\" この設定をオンにすると、ツイートを送信した後に、パネルが自動で折りたたまれます。\"),_c('br'),_vm._v(\" ツイートするとき以外はできるだけ映像を大きくして見たい方におすすめです。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"fold_panel_after_sending_tweet\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.fold_panel_after_sending_tweet),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"fold_panel_after_sending_tweet\", $$v)},expression:\"settingsStore.settings.fold_panel_after_sending_tweet\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"reset_hashtag_when_program_switches\"}},[_vm._v(\"番組が切り替わったときにハッシュタグフォームをリセットする\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"reset_hashtag_when_program_switches\"}},[_vm._v(\" チャンネルを切り替えたときや、視聴中の番組が終了し次の番組の放送が開始されたときに、ハッシュタグフォームをリセットするかを設定します。\"),_c('br'),_vm._v(\" この設定をオンにしておけば、「誤って前番組のハッシュタグをつけたまま次番組の実況ツイートをしてしまう」といったミスを回避できます。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"reset_hashtag_when_program_switches\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.reset_hashtag_when_program_switches),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"reset_hashtag_when_program_switches\", $$v)},expression:\"settingsStore.settings.reset_hashtag_when_program_switches\"}})],1),_c('div',{staticClass:\"settings__item settings__item--switch\"},[_c('label',{staticClass:\"settings__item-heading\",attrs:{\"for\":\"auto_add_watching_channel_hashtag\"}},[_vm._v(\"視聴中のチャンネルに対応する局タグを自動で追加する\")]),_c('label',{staticClass:\"settings__item-label\",attrs:{\"for\":\"auto_add_watching_channel_hashtag\"}},[_vm._v(\" この設定をオンにすると、視聴中のチャンネルに対応する局タグ (#nhk, #tokyomx など) がハッシュタグフォームに自動で追加されます。\"),_c('br'),_vm._v(\" 現時点で、局タグは三大首都圏の地上波・BS の一部チャンネル・AT-X にのみ対応しています。\"),_c('br')]),_c('v-switch',{staticClass:\"settings__item-switch\",attrs:{\"id\":\"auto_add_watching_channel_hashtag\",\"inset\":\"\",\"hide-details\":\"\"},model:{value:(_vm.settingsStore.settings.auto_add_watching_channel_hashtag),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"auto_add_watching_channel_hashtag\", $$v)},expression:\"settingsStore.settings.auto_add_watching_channel_hashtag\"}})],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"デフォルトで表示される Twitter タブ内のタブ\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" 視聴画面を開いたときに、パネルの Twitter タブの中で最初に表示されるタブを設定します。\"),_c('br')]),_c('v-select',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"items\":_vm.twitter_active_tab},model:{value:(_vm.settingsStore.settings.twitter_active_tab),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"twitter_active_tab\", $$v)},expression:\"settingsStore.settings.twitter_active_tab\"}})],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"ツイートにつけるハッシュタグの位置\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" ツイート本文から見て、ハッシュタグをどの位置につけてツイートするかを設定します。\"),_c('br')]),_c('v-select',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"items\":_vm.tweet_hashtag_position},model:{value:(_vm.settingsStore.settings.tweet_hashtag_position),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"tweet_hashtag_position\", $$v)},expression:\"settingsStore.settings.tweet_hashtag_position\"}})],1),_c('div',{staticClass:\"settings__item\"},[_c('div',{staticClass:\"settings__item-heading\"},[_vm._v(\"ツイートするキャプチャに番組タイトルの透かしを描画する\")]),_c('div',{staticClass:\"settings__item-label\"},[_vm._v(\" ツイートするキャプチャに、透かしとして視聴中の番組タイトルを描画するかを設定します。\"),_c('br'),_vm._v(\" 透かしの描画位置は 左上・右上・左下・右下 から選択できます。\"),_c('br')]),_c('v-select',{staticClass:\"settings__item-form\",attrs:{\"outlined\":\"\",\"hide-details\":\"\",\"dense\":_vm.is_form_dense,\"items\":_vm.tweet_capture_watermark_position},model:{value:(_vm.settingsStore.settings.tweet_capture_watermark_position),callback:function ($$v) {_vm.$set(_vm.settingsStore.settings, \"tweet_capture_watermark_position\", $$v)},expression:\"settingsStore.settings.tweet_capture_watermark_position\"}})],1)]),_c('v-overlay',{attrs:{\"value\":_vm.is_twitter_password_auth_sending,\"z-index\":\"300\"}},[_c('v-progress-circular',{attrs:{\"color\":\"secondary\",\"indeterminate\":\"\",\"size\":\"64\"}})],1)],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\nimport Message from '@/message';\nimport APIClient from '@/services/APIClient';\n\n\n/** Twitter アカウントと連携するための認証 URL を表すインターフェイス */\nexport interface ITwitterAuthURL {\n authorization_url: string;\n}\n\n/** ツイートの送信結果を表すインターフェイス */\nexport interface ITweetResult {\n is_success: boolean;\n tweet_url?: string;\n detail: string;\n}\n\nexport interface ITwitterPasswordAuthRequest {\n screen_name: string;\n password: string;\n}\n\n\nclass Twitter {\n\n /**\n * Twitter アカウントと連携するための認証 URL を取得する\n * @returns 認証 URL or 認証 URL の取得に失敗した場合は null\n */\n static async fetchAuthorizationURL(): Promise {\n\n // API リクエストを実行\n const response = await APIClient.get('/twitter/auth');\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'Twitter アカウントとの連携用の認証 URL を取得できませんでした。');\n return null;\n }\n\n return response.data.authorization_url;\n }\n\n\n /**\n * Twitter アカウントとパスワード認証で連携する\n * @param twitter_password_auth_request スクリーンネームとパスワード\n * @returns ログインできた場合は true, 失敗した場合は false\n */\n static async authWithPassword(twitter_password_auth_request: ITwitterPasswordAuthRequest): Promise {\n\n // API リクエストを実行\n const response = await APIClient.post('/twitter/password-auth', twitter_password_auth_request);\n\n // エラー処理\n if ('is_error' in response) {\n if (response.error.message.startsWith('Failed to authenticate with password')) {\n const error = response.error.message.match(/Message: (.+)\\)/)[1];\n Message.error(`ログインに失敗しました。${error}`);\n } else if (response.error.message.startsWith('Unexpected error occurred while authenticate with password')) {\n const error = response.error.message.match(/Message: (.+)\\)/)[1];\n Message.error(`ログインフローの途中で予期せぬエラーが発生しました。${error}`);\n } else if (response.error.message.startsWith('Failed to get user information')) {\n Message.error('Twitter アカウントのユーザー情報の取得に失敗しました。');\n } else {\n APIClient.showGenericError(response, 'Twitter アカウントとの連携に失敗しました。');\n }\n return false;\n }\n\n return true;\n }\n\n\n /**\n * 現在ログイン中のユーザーアカウントに紐づく Twitter アカウントとの連携を解除する\n * @param screen_name Twitter のスクリーンネーム\n * @returns 連携解除に成功した場合は true, 失敗した場合は false\n */\n static async logoutAccount(screen_name: string): Promise {\n\n // API リクエストを実行\n const response = await APIClient.delete(`/twitter/accounts/${screen_name}`);\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'Twitter アカウントとの連携を解除できませんでした。');\n return false;\n }\n\n return true;\n }\n\n\n /**\n * ツイートを送信する\n * @param screen_name Twitter のスクリーンネーム\n * @param text ツイート本文\n * @param captures 添付するキャプチャ画像\n */\n static async sendTweet(screen_name: string, text: string, captures: Blob[]): Promise<{message: string; is_error: boolean;}> {\n\n // multipart/form-data でツイート本文と画像(選択されている場合)を送る\n const form_data = new FormData();\n form_data.append('tweet', text);\n for (const tweet_capture of captures) {\n form_data.append('images', tweet_capture);\n }\n\n // API リクエストを実行\n const response = await APIClient.post(`/twitter/accounts/${screen_name}/tweets`, form_data, {\n headers: {'Content-Type': 'multipart/form-data'},\n });\n\n // エラー処理 (API リクエスト自体に失敗した場合)\n if ('is_error' in response) {\n if (response.error.message) {\n if (Number.isNaN(response.status)) {\n return {message: `エラー: ツイートの送信に失敗しました。(${response.error.message})`, is_error: true};\n } else {\n return {message: `エラー: ツイートの送信に失敗しました。(HTTP Error ${response.status} / ${response.error.message})`, is_error: true};\n }\n } else {\n return {message: `エラー: ツイートの送信に失敗しました。(HTTP Error ${response.status})`, is_error: true};\n }\n }\n\n // 成功 or 失敗に関わらず detail の内容をそのまま通知する\n if (response.data.is_success === true) {\n // ツイート成功\n return {message: response.data.detail, is_error: false};\n } else {\n // ツイート失敗\n return {message: `エラー: ${response.data.detail}`, is_error: true};\n }\n }\n}\n\nexport default Twitter;\n","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport Twitter from '@/services/Twitter';\nimport useSettingsStore from '@/store/SettingsStore';\nimport useUserStore from '@/store/UserStore';\nimport Utils from '@/utils';\nimport SettingsBase from '@/views/Settings/Base.vue';\n\nexport default Vue.extend({\n name: 'Settings-Twitter',\n components: {\n SettingsBase,\n },\n data() {\n return {\n\n // フォームを小さくするかどうか\n is_form_dense: Utils.isSmartphoneHorizontal(),\n\n // デフォルトで表示されるパネルのタブの選択肢\n twitter_active_tab: [\n {text: 'ツイート検索タブ', value: 'Search'},\n {text: 'タイムラインタブ', value: 'Timeline'},\n {text: 'キャプチャタブ', value: 'Capture'},\n ],\n\n // ツイートにつけるハッシュタグの位置の選択肢\n tweet_hashtag_position: [\n {text: 'ツイート本文の前に追加する', value: 'Prepend'},\n {text: 'ツイート本文の後に追加する', value: 'Append'},\n {text: 'ツイート本文の前に追加してから改行する', value: 'PrependWithLineBreak'},\n {text: 'ツイート本文の後に改行してから追加する', value: 'AppendWithLineBreak'},\n ],\n\n // ツイートするキャプチャに番組タイトルの透かしを描画する位置の選択肢\n tweet_capture_watermark_position: [\n {text: '透かしを描画しない', value: 'None'},\n {text: '透かしをキャプチャの左上に描画する', value: 'TopLeft'},\n {text: '透かしをキャプチャの右上に描画する', value: 'TopRight'},\n {text: '透かしをキャプチャの左下に描画する', value: 'BottomLeft'},\n {text: '透かしをキャプチャの右下に描画する', value: 'BottomRight'},\n ],\n\n // ローディング中かどうか\n is_loading: true,\n\n // パスワード認証実行中かどうか\n is_twitter_password_auth_sending: false,\n\n // パスワード認証用ダイヤログ\n twitter_password_auth_dialog: false,\n\n // Twitter のスクリーンネームとパスワード\n twitter_screen_name: '',\n twitter_password: '',\n twitter_password_showing: false,\n };\n },\n computed: {\n // SettingsStore / UserStore に this.settingsStore / this.userStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useSettingsStore, useUserStore),\n },\n async created() {\n\n // アカウント情報を更新\n await this.userStore.fetchUser();\n\n // ローディング状態を解除\n this.is_loading = false;\n\n // もしハッシュ (# から始まるフラグメント) に何か指定されていたら、\n // OAuth 連携のコールバックの結果が入っている可能性が高いので、パースを試みる\n // アカウント情報更新より後にしないと Snackbar がうまく表示されない\n if (location.hash !== '') {\n const params = new URLSearchParams(location.hash.replace('#', ''));\n if (params.get('status') !== null && params.get('detail') !== null) {\n // コールバックの結果を取得できたので、OAuth 連携の結果を画面に通知する\n const authorization_status = parseInt(params.get('status')!);\n const authorization_detail = params.get('detail')!;\n this.onOAuthCallbackReceived(authorization_status, authorization_detail);\n // URL からフラグメントを削除\n // ref: https://stackoverflow.com/a/49373716/17124142\n history.replaceState(null, '', ' ');\n }\n }\n },\n methods: {\n async loginTwitterAccountWithPasswordForm() {\n // ログインしていない場合はエラーにする\n if (this.userStore.is_logged_in === false) {\n this.$message.warning('連携をはじめるには、KonomiTV アカウントにログインしてください。');\n await Utils.sleep(0.01);\n this.twitter_password_auth_dialog = false;\n return;\n }\n this.twitter_password_auth_dialog = true;\n },\n\n async loginTwitterAccountWithPassword() {\n\n // バリデーションを実行\n if ((this.$refs.twitter_form as any).validate() === false) {\n return;\n }\n\n // Twitter パスワード認証 API にリクエスト\n this.is_twitter_password_auth_sending = true;\n const result = await Twitter.authWithPassword({\n screen_name: this.twitter_screen_name,\n password: this.twitter_password,\n });\n this.is_twitter_password_auth_sending = false;\n if (result === false) {\n return;\n }\n\n // アカウント情報を強制的に更新\n await this.userStore.fetchUser(true);\n if (this.userStore.user === null) {\n this.$message.error('アカウント情報を取得できませんでした。');\n return;\n }\n\n // ログイン中のユーザーに紐づく Twitter アカウントのうち、一番 updated_at が新しいものを取得\n // ログインすると updated_at が更新されるため、この時点で一番 updated_at が新しいアカウントが今回連携したものだと判断できる\n // ref: https://stackoverflow.com/a/12192544/17124142 (ISO8601 のソートアルゴリズム)\n const current_twitter_account = [...this.userStore.user.twitter_accounts].sort((a, b) => {\n return (a.updated_at < b.updated_at) ? 1 : ((a.updated_at > b.updated_at) ? -1 : 0);\n })[0];\n\n this.$message.success(`Twitter @${current_twitter_account.screen_name} と連携しました。`);\n\n // フォームをリセットし、非表示にする\n (this.$refs.twitter_form as any).reset();\n this.twitter_password_auth_dialog = false;\n },\n\n async loginTwitterAccountWithOAuth() {\n\n // ログインしていない場合はエラーにする\n if (this.userStore.is_logged_in === false) {\n this.$message.warning('連携をはじめるには、KonomiTV アカウントにログインしてください。');\n return;\n }\n\n // Twitter アカウントと連携するための認証 URL を取得\n const authorization_url = await Twitter.fetchAuthorizationURL();\n if (authorization_url === null) {\n return;\n }\n\n // モバイルデバイスではポップアップが事実上使えない (特に Safari ではブロックされてしまう) ので、素直にリダイレクトで実装する\n if (Utils.isMobileDevice() === true) {\n location.href = authorization_url;\n return;\n }\n\n // OAuth 連携のため、認証 URL をポップアップウインドウで開く\n // window.open() の第2引数はユニークなものにしておくと良いらしい\n // ref: https://qiita.com/catatsuy/items/babce8726ea78f5d25b1 (大変参考になりました)\n const popup_window = window.open(authorization_url, 'KonomiTV-OAuthPopup', Utils.getWindowFeatures());\n if (popup_window === null) {\n this.$message.error('ポップアップウインドウを開けませんでした。');\n return;\n }\n\n // 認証完了 or 失敗後、ポップアップウインドウから送信される文字列を受信\n const onMessage = async (event) => {\n\n // すでにウインドウが閉じている場合は実行しない\n if (popup_window.closed) return;\n\n // 受け取ったオブジェクトに KonomiTV-OAuthPopup キーがない or そもそもオブジェクトではない際は実行しない\n // ブラウザの拡張機能から結構余計な message が飛んでくるっぽい…。\n if (Utils.typeof(event.data) !== 'object') return;\n if (('KonomiTV-OAuthPopup' in event.data) === false) return;\n\n // 認証は完了したので、ポップアップウインドウを閉じ、リスナーを解除する\n if (popup_window) popup_window.close();\n window.removeEventListener('message', onMessage);\n\n // ステータスコードと詳細メッセージを取得\n const authorization_status = event.data['KonomiTV-OAuthPopup']['status'] as number;\n const authorization_detail = event.data['KonomiTV-OAuthPopup']['detail'] as string;\n this.onOAuthCallbackReceived(authorization_status, authorization_detail);\n };\n\n // postMessage() を受信するリスナーを登録\n window.addEventListener('message', onMessage);\n },\n\n async onOAuthCallbackReceived(authorization_status: number, authorization_detail: string) {\n console.log(`TwitterAuthCallbackAPI: Status: ${authorization_status} / Detail: ${authorization_detail}`);\n\n // OAuth 連携に失敗した\n if (authorization_status !== 200) {\n if (authorization_detail.startsWith('Authorization was denied by user')) {\n this.$message.error('Twitter アカウントとの連携がキャンセルされました。');\n } else if (authorization_detail.startsWith('Failed to get access token')) {\n this.$message.error('アクセストークンの取得に失敗しました。');\n } else if (authorization_detail.startsWith('Failed to get user information')) {\n this.$message.error('Twitter アカウントのユーザー情報の取得に失敗しました。');\n } else {\n this.$message.error(`Twitter アカウントとの連携に失敗しました。(${authorization_detail})`);\n }\n return;\n }\n\n // アカウント情報を強制的に更新\n await this.userStore.fetchUser(true);\n if (this.userStore.user === null) {\n this.$message.error('アカウント情報を取得できませんでした。');\n return;\n }\n\n // ログイン中のユーザーに紐づく Twitter アカウントのうち、一番 updated_at が新しいものを取得\n // ログインすると updated_at が更新されるため、この時点で一番 updated_at が新しいアカウントが今回連携したものだと判断できる\n // ref: https://stackoverflow.com/a/12192544/17124142 (ISO8601 のソートアルゴリズム)\n const current_twitter_account = [...this.userStore.user.twitter_accounts].sort((a, b) => {\n return (a.updated_at < b.updated_at) ? 1 : ((a.updated_at > b.updated_at) ? -1 : 0);\n })[0];\n\n this.$message.success(`Twitter @${current_twitter_account.screen_name} と連携しました。`);\n },\n\n async logoutTwitterAccount(screen_name: string) {\n\n // Twitter アカウント連携解除 API にリクエスト\n const result = await Twitter.logoutAccount(screen_name);\n if (result === false) {\n return;\n }\n\n // アカウント情報を強制的に更新\n await this.userStore.fetchUser(true);\n\n this.$message.success(`Twitter @${screen_name} との連携を解除しました。`);\n },\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Twitter.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Twitter.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Twitter.vue?vue&type=template&id=ea90430c&scoped=true&\"\nimport script from \"./Twitter.vue?vue&type=script&lang=ts&\"\nexport * from \"./Twitter.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Twitter.vue?vue&type=style&index=0&id=ea90430c&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"ea90430c\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"route-container\"},[_c('Header'),_c('main',[_c('Navigation'),_c('div',{staticClass:\"channels-container channels-container--home\",class:{'channels-container--loading': _vm.is_loading}},[_c('v-tabs-fix',{staticClass:\"channels-tab\",attrs:{\"centered\":\"\"},model:{value:(_vm.tab),callback:function ($$v) {_vm.tab=$$v},expression:\"tab\"}},_vm._l((Array.from(_vm.channelsStore.channels_list_with_pinned)),function([channels_type,]){return _c('v-tab',{key:channels_type,staticClass:\"channels-tab__item\"},[_vm._v(\" \"+_vm._s(channels_type)+\" \")])}),1),_c('v-tabs-items-fix',{staticClass:\"channels-list\",model:{value:(_vm.tab),callback:function ($$v) {_vm.tab=$$v},expression:\"tab\"}},_vm._l((Array.from(_vm.channelsStore.channels_list_with_pinned)),function([channels_type, channels]){return _c('v-tab-item-fix',{key:channels_type,staticClass:\"channels-tabitem\"},[_c('div',{staticClass:\"channels\",class:`channels--tab-${channels_type} channels--length-${channels.length}`},[_vm._l((channels),function(channel){return _c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],key:channel.id,staticClass:\"channel\",attrs:{\"to\":`/tv/watch/${channel.channel_id}`}},[_c('div',{staticClass:\"channel__broadcaster\"},[_c('img',{staticClass:\"channel__broadcaster-icon\",attrs:{\"src\":`${_vm.Utils.api_base_url}/channels/${channel.channel_id}/logo`}}),_c('div',{staticClass:\"channel__broadcaster-content\"},[_c('span',{staticClass:\"channel__broadcaster-name\"},[_vm._v(\"Ch: \"+_vm._s(channel.channel_number)+\" \"+_vm._s(channel.channel_name))]),_c('div',{staticClass:\"channel__broadcaster-status\"},[_c('div',{staticClass:\"channel__broadcaster-status-force\",class:`channel__broadcaster-status-force--${_vm.ChannelUtils.getChannelForceType(channel.channel_force)}`},[_c('Icon',{attrs:{\"icon\":\"fa-solid:fire-alt\",\"height\":\"12px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(\"勢い:\")]),_c('span',{staticClass:\"ml-1\"},[_vm._v(_vm._s(_vm.ProgramUtils.getAttribute(channel, 'channel_force', '--')))]),_c('span',{staticStyle:{\"margin-left\":\"3px\"}},[_vm._v(\" コメ/分\")])],1),_c('div',{staticClass:\"channel__broadcaster-status-viewers ml-4\"},[_c('Icon',{attrs:{\"icon\":\"fa-solid:eye\",\"height\":\"14px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(\"視聴数:\")]),_c('span',{staticClass:\"ml-1\"},[_vm._v(_vm._s(channel.viewers))])],1)])]),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip\",value:(_vm.isPinnedChannel(channel.channel_id) ? 'ピン留めを外す' : 'ピン留めする'),expression:\"isPinnedChannel(channel.channel_id) ? 'ピン留めを外す' : 'ピン留めする'\"}],staticClass:\"channel__broadcaster-pin\",class:{'channel__broadcaster-pin--pinned': _vm.isPinnedChannel(channel.channel_id)},on:{\"click\":function($event){$event.preventDefault();$event.stopPropagation();_vm.isPinnedChannel(channel.channel_id) ? _vm.removePinnedChannel(channel.channel_id) : _vm.addPinnedChannel(channel.channel_id)},\"mousedown\":function($event){$event.preventDefault();$event.stopPropagation();/* 親要素の波紋が広がらないように */}}},[_c('Icon',{attrs:{\"icon\":\"fluent:pin-20-filled\",\"width\":\"24px\"}})],1)]),_c('div',{staticClass:\"channel__program-present\"},[_c('div',{staticClass:\"channel__program-present-title-wrapper\"},[_c('span',{staticClass:\"channel__program-present-title\",domProps:{\"innerHTML\":_vm._s(_vm.ProgramUtils.decorateProgramInfo(channel.program_present, 'title'))}}),_c('span',{staticClass:\"channel__program-present-time\"},[_vm._v(_vm._s(_vm.ProgramUtils.getProgramTime(channel.program_present)))])]),_c('span',{staticClass:\"channel__program-present-description\",domProps:{\"innerHTML\":_vm._s(_vm.ProgramUtils.decorateProgramInfo(channel.program_present, 'description'))}})]),_c('v-spacer'),_c('div',{staticClass:\"channel__program-following\"},[_c('div',{staticClass:\"channel__program-following-title\"},[_c('span',{staticClass:\"channel__program-following-title-decorate\"},[_vm._v(\"NEXT\")]),_c('Icon',{staticClass:\"channel__program-following-title-icon\",attrs:{\"icon\":\"fluent:fast-forward-20-filled\",\"width\":\"16px\"}}),_c('span',{staticClass:\"channel__program-following-title-text\",domProps:{\"innerHTML\":_vm._s(_vm.ProgramUtils.decorateProgramInfo(channel.program_following, 'title'))}})],1),_c('span',{staticClass:\"channel__program-following-time\"},[_vm._v(_vm._s(_vm.ProgramUtils.getProgramTime(channel.program_following)))])]),_c('div',{staticClass:\"channel__progressbar\"},[_c('div',{staticClass:\"channel__progressbar-progress\",style:(`width:${_vm.ProgramUtils.getProgramProgress(channel.program_present)}%;`)})])],1)}),(channels_type === 'ピン留め' && channels.length === 0)?_c('div',{staticClass:\"pinned-container d-flex justify-center align-center w-100\"},[_c('div',{staticClass:\"d-flex justify-center align-center flex-column\"},[_c('h2',[_vm._v(\"ピン留めされているチャンネルが\"),_c('br'),_vm._v(\"ありません。\")]),_c('div',{staticClass:\"mt-4 text--text text--darken-1\"},[_vm._v(\"各チャンネルの \"),_c('Icon',{staticStyle:{\"position\":\"relative\",\"bottom\":\"-5px\"},attrs:{\"icon\":\"fluent:pin-20-filled\",\"width\":\"22px\"}}),_vm._v(\" アイコンから、よくみる\"),_c('br'),_vm._v(\"チャンネルをこのタブにピン留めできます。\")],1),_c('div',{staticClass:\"mt-2 text--text text--darken-1\"},[_vm._v(\"チャンネルをピン留めすると、\"),_c('br'),_vm._v(\"このタブが最初に表示されます。\")])])]):_vm._e()],2)])}),1)],1)],1)],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n/** 番組情報を表すインターフェイス */\nexport interface IProgram {\n id: string;\n network_id: number;\n service_id: number;\n event_id: number;\n channel_id: string;\n title: string;\n description: string;\n detail: {[key: string]: string};\n start_time: string;\n end_time: string;\n duration: number;\n is_free: boolean;\n genre: {major: string; middle: string}[];\n video_type: string;\n video_codec: string;\n video_resolution: string;\n primary_audio_type: string;\n primary_audio_language: string;\n primary_audio_sampling_rate: string;\n secondary_audio_type: string | null;\n secondary_audio_language: string | null;\n secondary_audio_sampling_rate: string | null;\n}\n\n/** 番組情報を表すインターフェイスのデフォルト値 */\nexport const IProgramDefault: IProgram = {\n id: 'NID0-SID0-EID0',\n network_id: 0,\n service_id: 0,\n event_id: 0,\n channel_id: 'gr000',\n title: '取得中…',\n description: '取得中…',\n detail: {},\n start_time: '2000-01-01T00:00:00+09:00',\n end_time: '2000-01-01T00:00:00+09:00',\n duration: 0,\n is_free: true,\n genre: [],\n video_type: '映像1080i(1125i)、アスペクト比16:9 パンベクトルなし',\n video_codec: 'mpeg2',\n video_resolution: '1080i',\n primary_audio_type: '2/0モード(ステレオ)',\n primary_audio_language: '日本語',\n primary_audio_sampling_rate: '48kHz',\n secondary_audio_type: null,\n secondary_audio_language: null,\n secondary_audio_sampling_rate: null,\n};\n\n// TODO: 番組情報 API が開発されたらここに API 定義を書く\n","\nimport APIClient from '@/services/APIClient';\nimport { IProgram, IProgramDefault } from '@/services/Programs';\n\n\n/** チャンネルタイプの型 */\nexport type ChannelType = 'GR' | 'BS' | 'CS' | 'CATV' | 'SKY' | 'STARDIGIO';\n\n// チャンネルタイプの型 (実際のチャンネルリストに表示される表現)\nexport type ChannelTypePretty = 'ピン留め' | '地デジ' | 'BS' | 'CS' | 'CATV' | 'SKY' | 'StarDigio';\n\n/** すべてのチャンネルタイプのチャンネルの情報を表すインターフェイス */\nexport interface IChannelsList {\n GR: IChannel[];\n BS: IChannel[];\n CS: IChannel[];\n CATV: IChannel[];\n SKY: IChannel[];\n STARDIGIO: IChannel[];\n}\n\n/** チャンネル情報を表すインターフェイス */\nexport interface IChannel {\n id: string;\n network_id: number;\n service_id: number;\n transport_stream_id: number | null;\n remocon_id: number | null;\n channel_id: string;\n channel_number: string;\n channel_name: string;\n channel_type: ChannelType;\n channel_force: number | null;\n channel_comment: number | null;\n is_subchannel: boolean;\n is_radiochannel: boolean;\n is_display: boolean;\n viewers: number;\n program_present: IProgram | null;\n program_following: IProgram | null;\n}\n\n/** チャンネル情報を表すインターフェイスのデフォルト値 */\nexport const IChannelDefault: IChannel = {\n id: 'NID0-SID0',\n network_id: 0,\n service_id: 0,\n transport_stream_id: null,\n remocon_id: null,\n channel_id: 'gr000',\n channel_number: '---',\n channel_name: '取得中…',\n channel_type: 'GR',\n channel_force: null,\n channel_comment: null,\n is_subchannel: false,\n is_radiochannel: false,\n is_display: true,\n viewers: 0,\n program_present: IProgramDefault,\n program_following: IProgramDefault,\n};\n\n/** ニコニコ実況のセッション情報を表すインターフェイス */\nexport interface IJikkyoSession {\n is_success: boolean;\n audience_token: string | null;\n detail: string;\n}\n\n\nclass Channels {\n\n /**\n * すべてのチャンネルの情報を取得する\n * @return すべてのチャンネルの情報\n */\n static async fetchAll(): Promise {\n\n // API リクエストを実行\n const response = await APIClient.get('/channels');\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'チャンネル情報を取得できませんでした。');\n return null;\n }\n\n return response.data;\n }\n\n\n /**\n * 指定したチャンネルの情報を取得する\n * 現状、処理の見直しにより使用されていない\n * @param channel_id チャンネル ID\n * @return 指定したチャンネルの情報\n */\n static async fetch(channel_id: string): Promise {\n\n // API リクエストを実行\n const response = await APIClient.get(`/channels/${channel_id}`);\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'チャンネル情報を取得できませんでした。');\n return null;\n }\n\n return response.data;\n }\n\n\n /**\n * 指定したチャンネルに紐づくニコニコ実況のセッション情報を取得する\n * @param channel_id チャンネル ID\n * @return 指定したチャンネルに紐づくニコニコ実況のセッション情報\n */\n static async fetchJikkyoSession(channel_id: string): Promise {\n\n // API リクエストを実行\n const response = await APIClient.get(`/channels/${channel_id}/jikkyo`);\n\n // エラー処理\n if ('is_error' in response) {\n APIClient.showGenericError(response, 'ニコニコ実況のセッション情報を取得できませんでした。');\n return null;\n }\n\n return response.data;\n }\n}\n\nexport default Channels;\n","\nimport { defineStore } from 'pinia';\nimport Vue from 'vue';\n\nimport Channels, { ChannelType, ChannelTypePretty, IChannelsList, IChannel, IChannelDefault } from '@/services/Channels';\nimport useSettingsStore from '@/store/SettingsStore';\nimport Utils, { ChannelUtils } from '@/utils';\n\n\n/**\n * TV ホーム画面と TV 視聴画面の両方のページでチャンネル情報を共有するためのストア\n * チャンネル情報の API からの取得はかなり重めなので、ページ遷移時に毎回 API リクエストを行うのはパフォーマンスが悪い\n * チャンネル情報をストアに格納しておくことで、TV ホーム画面から TV 視聴画面に遷移したときのパフォーマンスが向上する\n */\nconst useChannelsStore = defineStore('channels', {\n state: () => ({\n\n // 現在視聴中のチャンネルの ID (ex: gr011)\n // 視聴画面のみ有効で、ホーム画面では利用されない\n channel_id: 'gr000' as string,\n\n // すべてのチャンネルタイプのチャンネルリスト\n channels_list: {\n GR: [],\n BS: [],\n CS: [],\n CATV: [],\n SKY: [],\n STARDIGIO: [],\n } as IChannelsList,\n\n // 初回のチャンネル情報更新が実行された後かどうか\n is_channels_list_initial_updated: false,\n\n // 最終更新日時 (UNIX タイムスタンプ、秒単位)\n last_updated_at: 0,\n }),\n getters: {\n\n /**\n * ライブ視聴画面を表示中かどうか\n * チャンネル情報がセットされているかどうかで判定できる\n */\n is_showing_live(): boolean {\n return this.channel_id !== 'gr000';\n },\n\n /**\n * 前・現在・次のチャンネル情報 (視聴画面用)\n * チャンネル情報はデータ量がかなり多いので、個別に取得するより一気に取得したほうがループ回数が少なくなりパフォーマンスが良い\n */\n channel(): {previous: IChannel; current: IChannel; next: IChannel;} {\n\n // チャンネルタイプごとのチャンネル情報リストを取得する (すべてのチャンネルリストから探索するより効率的)\n const channels: IChannel[] | undefined = this.channels_list[ChannelUtils.getChannelType(this.channel_id)];\n\n // まだチャンネルリストの更新が終わっていないなどの場合で取得できなかった場合、\n // null を返すと UI 側でのエラー処理が大変なので、暫定的なダミーのチャンネル情報を返す\n if (channels === undefined || channels.length === 0) {\n return {\n previous: IChannelDefault,\n current: IChannelDefault,\n next: IChannelDefault,\n };\n }\n\n // 起点にするチャンネル情報があるインデックスを取得\n const current_channel_index = channels.findIndex((channel) => channel.channel_id === this.channel_id);\n\n // インデックスが取得できなかった場合も同様に、暫定的なダミーのチャンネル情報を返す\n if (current_channel_index === -1) {\n const IProgramError = {\n ...IChannelDefault.program_present,\n channel_id: 'gr999',\n title: 'チャンネル情報取得エラー',\n description: 'このチャンネル ID のチャンネル情報は存在しません。',\n };\n const IChannelError = {\n ...IChannelDefault,\n channel_id: 'gr999', // チャンネル情報が存在しないことを示す特殊なチャンネル ID\n channel_name: 'ERROR',\n program_present: IProgramError,\n program_following: IProgramError,\n };\n return {\n previous: IChannelError,\n current: IChannelError,\n next: IChannelError,\n };\n }\n\n // 前のインデックスを取得する\n // インデックスがマイナスになった時は、最後のインデックスに巻き戻す\n // channel.is_display が true のチャンネルに到達するまで続ける\n const previous_channel_index = ((): number => {\n let index = current_channel_index - 1;\n while (channels.length) {\n if (index <= -1) {\n index = channels.length - 1; // 最後のインデックス\n }\n if (channels[index].is_display) {\n return index;\n }\n index--;\n }\n return 0;\n })();\n\n // 次のインデックスを取得する\n // インデックスが配列の長さを超えた時は、最初のインデックスに巻き戻す\n // channel.is_display が true のチャンネルに到達するまで続ける\n const next_channel_index = ((): number => {\n let index = current_channel_index + 1;\n while (channels.length) {\n if (index >= channels.length) {\n index = 0; // 最初のインデックス\n }\n if (channels[index].is_display) {\n return index;\n }\n index++;\n }\n return 0;\n })();\n\n // 前・現在・次のチャンネル情報を返す\n return {\n previous: channels[previous_channel_index],\n current: channels[current_channel_index],\n next: channels[next_channel_index],\n };\n },\n\n /**\n * 実際に表示されるチャンネルリストを表すデータ\n * ピン留めチャンネルのタブを追加するほか、放送していないサブチャンネルはピン留めタブを含めて表示から除外される\n * また、チャンネルが1つもないチャンネルタイプのタブも表示から除外される\n * (たとえば SKY (スカパー!プレミアムサービス) のタブは、SKY に属すチャンネルが1つもない(=受信できない)なら表示されない)\n */\n channels_list_with_pinned(): Map {\n\n const settings_store = useSettingsStore();\n\n // 事前に Map を定義しておく\n // Map にしていたのは、確か連想配列の順序を保証してくれるからだったはず\n const channels_list_with_pinned = new Map();\n channels_list_with_pinned.set('ピン留め', []);\n channels_list_with_pinned.set('地デジ', []);\n\n // 初回のチャンネル情報更新がまだ実行されていない or 実行中のときは最低限のこの2つだけで返す\n if (this.is_channels_list_initial_updated === false) {\n return channels_list_with_pinned;\n }\n\n channels_list_with_pinned.set('BS', []);\n channels_list_with_pinned.set('CS', []);\n channels_list_with_pinned.set('CATV', []);\n channels_list_with_pinned.set('SKY', []);\n channels_list_with_pinned.set('StarDigio', []);\n\n // channels_list に格納されているすべてのチャンネルに対しループを回し、\n // 順次 channels_list_with_pinned に追加していく\n // 1つのチャンネルに対するループ回数が少なくなる分、毎回 filter() や find() するよりも高速になるはず\n for (const [channel_type, channels] of Object.entries(this.channels_list)) {\n for (const channel of channels) {\n\n // 放送していないサブチャンネルは除外\n if (channel.is_display === false) {\n continue;\n }\n\n // ピン留めしているチャンネルの ID (ex: gr011) が入るリストに含まれていたら、ピン留めタブに追加\n if (settings_store.settings.pinned_channel_ids.includes(channel.channel_id)) {\n channels_list_with_pinned.get('ピン留め')?.push(channel);\n }\n\n // チャンネルタイプごとに分類\n switch (channel.channel_type) {\n case 'GR': {\n channels_list_with_pinned.get('地デジ')?.push(channel);\n break;\n }\n case 'BS': {\n channels_list_with_pinned.get('BS')?.push(channel);\n break;\n }\n case 'CS': {\n channels_list_with_pinned.get('CS')?.push(channel);\n break;\n }\n case 'CATV': {\n channels_list_with_pinned.get('CATV')?.push(channel);\n break;\n }\n case 'SKY': {\n channels_list_with_pinned.get('SKY')?.push(channel);\n break;\n }\n case 'STARDIGIO': {\n channels_list_with_pinned.get('StarDigio')?.push(channel);\n break;\n }\n }\n }\n }\n\n // ピン留めチャンネルを追加順に並び替える\n for (const channel of [...channels_list_with_pinned.get('ピン留め')!]) {\n const index = settings_store.settings.pinned_channel_ids.indexOf(channel.channel_id);\n channels_list_with_pinned.get('ピン留め')![index] = channel;\n }\n\n // 最後に、チャンネルが1つもないチャンネルタイプのタブを除外する (ピン留めタブを除く)\n for (const [channel_type, channels] of channels_list_with_pinned) {\n if (channel_type === 'ピン留め') {\n continue;\n }\n if (channels.length === 0) {\n channels_list_with_pinned.delete(channel_type);\n }\n }\n\n // ただし、this.channels_list_with_pinned 全体が空でもうピン留めタブしか残っていない場合は、ピン留めタブも削除する\n if (channels_list_with_pinned.size === 1 && channels_list_with_pinned.has('ピン留め')) {\n channels_list_with_pinned.delete('ピン留め');\n }\n\n return channels_list_with_pinned;\n },\n\n /**\n * 視聴画面向けの channels_list_with_pinned\n * 視聴画面ではピン留めされているチャンネルが1つもないときは、ピン留めタブを表示する必要性がないため削除される\n */\n channels_list_with_pinned_for_watch(): Map {\n const channels_list_with_pinned = new Map([...this.channels_list_with_pinned]);\n if (channels_list_with_pinned.get('ピン留め')?.length === 0) {\n channels_list_with_pinned.delete('ピン留め');\n }\n return channels_list_with_pinned;\n }\n },\n actions: {\n\n /**\n * 指定されたチャンネル ID のチャンネル情報を取得する\n * @param channel_id 取得するチャンネル ID (ex: gr011)\n * @returns チャンネル情報\n */\n getChannel(channel_id: string): IChannel | null {\n\n // チャンネルタイプごとのチャンネル情報リストを取得する (すべてのチャンネルリストから探索するより効率的)\n const channels = this.channels_list[ChannelUtils.getChannelType(channel_id)];\n if (channels === undefined) {\n return null;\n }\n\n // チャンネル ID が一致するチャンネル情報を返す\n return channels.find(channel => channel.channel_id === channel_id) ?? null;\n },\n\n /**\n * チャンネルタイプとリモコン番号からチャンネル情報を取得する\n * @param channel_type チャンネルタイプ\n * @param remocon_id リモコン番号\n * @returns チャンネル情報 (見つからなかった場合は null)\n */\n getChannelByRemoconID(channel_type: ChannelType, remocon_id: number): IChannel | null {\n\n // 指定されたチャンネルタイプのチャンネルを取得\n const channels = this.channels_list[channel_type];\n\n // リモコン番号が一致するチャンネルを取得\n const channel = channels.find((channel) => channel.remocon_id === remocon_id);\n\n // リモコン番号が一致するチャンネルを見つけられなかった場合は null を返す\n return channel ?? null;\n },\n\n /**\n * 指定されたチャンネル ID のチャンネル情報を更新する\n * 今のところ viewers (視聴者数) を更新する目的でしか使っていない\n * @param channel_id 更新するチャンネル ID (ex: gr011)\n * @param channel 更新後のチャンネル情報\n */\n updateChannel(channel_id: string, channel: IChannel): void {\n\n // チャンネルタイプごとのチャンネル情報リストを取得する (すべてのチャンネルリストから探索するより効率的)\n const channel_type = ChannelUtils.getChannelType(channel_id);\n if (this.channels_list[channel_type] === undefined) {\n return null;\n }\n\n // チャンネル ID が一致するチャンネル情報を更新する\n const index = this.channels_list[channel_type].findIndex(channel => channel.channel_id === channel_id);\n if (index === -1) {\n return;\n }\n // リアクティブにするために Vue.set を使う\n Vue.set(this.channels_list[channel_type], index, channel);\n },\n\n /**\n * チャンネルリストを更新する\n * @param force 強制的に更新するかどうか\n */\n async update(force: boolean = false): Promise {\n\n const update = async () => {\n\n // 最新のすべてのチャンネルの情報を取得\n const channels_list = await Channels.fetchAll();\n if (channels_list === null) {\n return;\n }\n\n this.channels_list = channels_list;\n this.is_channels_list_initial_updated = true;\n this.last_updated_at = Utils.time();\n };\n\n // すでに取得されている場合は更新しない\n if (this.is_channels_list_initial_updated === true && force === false) {\n\n // ただし、最終更新日時が1分以上前の場合は非同期で更新する\n if (Utils.time() - this.last_updated_at > 60) {\n update();\n }\n\n return;\n }\n\n // チャンネルリストの更新を行う\n await update();\n }\n }\n});\n\nexport default useChannelsStore;\n","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport Header from '@/components/Header.vue';\nimport Navigation from '@/components/Navigation.vue';\nimport useChannelsStore from '@/store/ChannelsStore';\nimport useSettingsStore from '@/store/SettingsStore';\nimport Utils, { ChannelUtils, ProgramUtils } from '@/utils';\n\nexport default Vue.extend({\n name: 'TV-Home',\n components: {\n Header,\n Navigation,\n },\n data() {\n return {\n\n // ユーティリティをテンプレートで使えるように\n Utils: Utils,\n ChannelUtils: ChannelUtils,\n ProgramUtils: ProgramUtils,\n\n // タブの状態管理\n tab: null as number | null,\n\n // ローディング中かどうか\n is_loading: true,\n\n // インターバル ID\n // ページ遷移時に setInterval(), setTimeout() の実行を止めるのに使う\n // setInterval(), setTimeout() の返り値を登録する\n interval_ids: [] as number[],\n };\n },\n computed: {\n // ChannelsStore / SettingsStore に this.channelsStore / this.settingsStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useChannelsStore, useSettingsStore),\n },\n // 開始時に実行\n async created() {\n\n // ピン留めされているチャンネルの ID が空なら、タブを地デジタブに切り替える\n // ピン留めができる事を示唆するためにピン留めタブ自体は残す\n if (this.settingsStore.settings.pinned_channel_ids.length === 0) {\n this.tab = 1;\n }\n\n // 00秒までの残り秒数を取得\n // 現在 16:01:34 なら 26 (秒) になる\n const residue_second = 60 - new Date().getSeconds();\n\n // 00秒になるまで待ってから実行するタイマー\n // 番組は基本1分単位で組まれているため、20秒や45秒など中途半端な秒数で更新してしまうと番組情報の反映が遅れてしまう\n this.interval_ids.push(window.setTimeout(() => {\n\n // この時点で00秒なので、チャンネル情報を更新\n this.channelsStore.update(true);\n\n // 以降、30秒おきにチャンネル情報を更新\n this.interval_ids.push(window.setInterval(() => this.channelsStore.update(true), 30 * 1000));\n\n }, residue_second * 1000));\n\n // チャンネル情報を更新 (初回)\n await this.channelsStore.update();\n\n // 少しだけ待つ\n // v-tabs-slider-wrapper をアニメーションさせないために必要\n await Utils.sleep(0.01);\n\n // この時点でピン留めされているチャンネルがないなら、タブを地デジタブに切り替える\n // ピン留めされているチャンネル自体はあるが、現在放送されていないため表示できない場合に備える\n if (this.channelsStore.channels_list_with_pinned.get('ピン留め').length === 0) {\n this.tab = 1;\n }\n\n // チャンネル情報の更新が終わったタイミングでローディング状態を解除する\n this.is_loading = false;\n },\n // 終了前に実行\n beforeDestroy() {\n\n // clearInterval() ですべての setInterval(), setTimeout() の実行を止める\n // clearInterval() と clearTimeout() は中身共通なので問題ない\n for (const interval_id of this.interval_ids) {\n window.clearInterval(interval_id);\n }\n },\n methods: {\n\n // チャンネルをピン留めする\n addPinnedChannel(channel_id: string) {\n\n // ピン留めするチャンネルの ID を追加 (保存は自動で行われる)\n this.settingsStore.settings.pinned_channel_ids.push(channel_id);\n\n const channel = this.channelsStore.getChannel(channel_id);\n this.$message.show(`${channel.channel_name}をピン留めしました。`);\n },\n\n // チャンネルをピン留めから外す\n removePinnedChannel(channel_id: string) {\n\n // ピン留めを外すチャンネルの ID を削除 (保存は自動で行われる)\n this.settingsStore.settings.pinned_channel_ids.splice(this.settingsStore.settings.pinned_channel_ids.indexOf(channel_id), 1);\n\n // この時点でピン留めされているチャンネルがないなら、タブを地デジタブに切り替える\n if (this.channelsStore.channels_list_with_pinned.get('ピン留め').length === 0) {\n this.tab = 1;\n }\n\n const channel = this.channelsStore.getChannel(channel_id);\n this.$message.show(`${channel.channel_name}のピン留めを外しました。`);\n },\n\n // チャンネルがピン留めされているか\n isPinnedChannel(channel_id: string): boolean {\n\n // 引数のチャンネルがピン留めリストに存在するかを返す\n return this.settingsStore.settings.pinned_channel_ids.includes(channel_id);\n }\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Home.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Home.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Home.vue?vue&type=template&id=5395b00e&scoped=true&\"\nimport script from \"./Home.vue?vue&type=script&lang=ts&\"\nexport * from \"./Home.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Home.vue?vue&type=style&index=0&id=5395b00e&prod&lang=scss&\"\nimport style1 from \"./Home.vue?vue&type=style&index=1&id=5395b00e&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"5395b00e\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"route-container\"},[_c('main',{staticClass:\"watch-container\",class:{\n 'watch-container--control-display': _vm.is_control_display,\n 'watch-container--panel-display': _vm.Utils.isSmartphoneVertical() ? true : _vm.is_panel_display,\n 'watch-container--fullscreen': _vm.is_fullscreen,\n }},[_c('nav',{staticClass:\"watch-navigation\",on:{\"mousemove\":function($event){return _vm.controlDisplayTimer($event)},\"touchmove\":function($event){return _vm.controlDisplayTimer($event)},\"click\":function($event){return _vm.controlDisplayTimer($event)}}},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"watch-navigation__icon\",attrs:{\"to\":\"/tv/\"}},[_c('img',{staticClass:\"watch-navigation__icon-image\",attrs:{\"src\":\"/assets/images/icon.svg\",\"width\":\"23px\"}})]),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.right\",value:('テレビをみる'),expression:\"'テレビをみる'\",modifiers:{\"right\":true}}],staticClass:\"watch-navigation__link\",attrs:{\"active-class\":\"watch-navigation__link--active\",\"to\":\"/tv/\"}},[_c('Icon',{staticClass:\"watch-navigation__link-icon\",attrs:{\"icon\":\"fluent:tv-20-regular\",\"width\":\"26px\"}})],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.right\",value:('ビデオをみる'),expression:\"'ビデオをみる'\",modifiers:{\"right\":true}}],staticClass:\"watch-navigation__link\",attrs:{\"active-class\":\"watch-navigation__link--active\",\"to\":\"/videos/\"}},[_c('Icon',{staticClass:\"watch-navigation__link-icon\",attrs:{\"icon\":\"fluent:movies-and-tv-20-regular\",\"width\":\"26px\"}})],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.right\",value:('番組表'),expression:\"'番組表'\",modifiers:{\"right\":true}}],staticClass:\"watch-navigation__link\",attrs:{\"active-class\":\"watch-navigation__link--active\",\"to\":\"/timetable/\"}},[_c('Icon',{staticClass:\"watch-navigation__link-icon\",attrs:{\"icon\":\"fluent:calendar-ltr-20-regular\",\"width\":\"26px\"}})],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.right\",value:('録画予約'),expression:\"'録画予約'\",modifiers:{\"right\":true}}],staticClass:\"watch-navigation__link\",attrs:{\"active-class\":\"watch-navigation__link--active\",\"to\":\"/reserves/\"}},[_c('Icon',{staticClass:\"watch-navigation__link-icon\",staticStyle:{\"padding\":\"0.5px\"},attrs:{\"icon\":\"fluent:timer-16-regular\",\"width\":\"26px\"}})],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.right\",value:('マイリスト'),expression:\"'マイリスト'\",modifiers:{\"right\":true}}],staticClass:\"watch-navigation__link\",attrs:{\"active-class\":\"watch-navigation__link--active\",\"to\":\"/mylist/\"}},[_c('Icon',{staticClass:\"watch-navigation__link-icon\",attrs:{\"icon\":\"ic:round-playlist-play\",\"width\":\"26px\"}})],1),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.right\",value:('キャプチャ'),expression:\"'キャプチャ'\",modifiers:{\"right\":true}}],staticClass:\"watch-navigation__link\",attrs:{\"active-class\":\"watch-navigation__link--active\",\"to\":\"/captures/\"}},[_c('Icon',{staticClass:\"watch-navigation__link-icon\",attrs:{\"icon\":\"fluent:image-multiple-24-regular\",\"width\":\"26px\"}})],1),_c('v-spacer'),_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.right\",value:('設定'),expression:\"'設定'\",modifiers:{\"right\":true}}],staticClass:\"watch-navigation__link\",attrs:{\"active-class\":\"watch-navigation__link--active\",\"to\":\"/settings/\"}},[_c('Icon',{staticClass:\"watch-navigation__link-icon\",attrs:{\"icon\":\"fluent:settings-20-regular\",\"width\":\"26px\"}})],1)],1),_c('div',{staticClass:\"watch-content\",on:{\"mousemove\":function($event){return _vm.controlDisplayTimer($event, true)},\"touchmove\":function($event){return _vm.controlDisplayTimer($event, true)},\"click\":function($event){return _vm.controlDisplayTimer($event, true)}}},[_c('header',{staticClass:\"watch-header\"},[_c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"watch-header__back-icon\",attrs:{\"to\":\"/tv/\"}},[_c('Icon',{attrs:{\"icon\":\"fluent:arrow-left-12-filled\",\"width\":\"25px\"}})],1),_c('img',{staticClass:\"watch-header__broadcaster\",attrs:{\"src\":`${_vm.Utils.api_base_url}/channels/${(_vm.channelsStore.channel_id)}/logo`}}),_c('span',{staticClass:\"watch-header__program-title\",domProps:{\"innerHTML\":_vm._s(_vm.ProgramUtils.decorateProgramInfo(_vm.channelsStore.channel.current.program_present, 'title'))}}),_c('span',{staticClass:\"watch-header__program-time\"},[_vm._v(\" \"+_vm._s(_vm.ProgramUtils.getProgramTime(_vm.channelsStore.channel.current.program_present, true))+\" \")]),_c('v-spacer'),_c('span',{staticClass:\"watch-header__now\"},[_vm._v(_vm._s(_vm.time))])],1),_c('div',{staticClass:\"watch-player\",class:{\n 'watch-player--loading': _vm.is_loading,\n 'watch-player--virtual-keyboard-display': _vm.is_virtual_keyboard_display && _vm.Utils.hasActiveElementClass('dplayer-comment-input'),\n }},[_c('div',{staticClass:\"watch-player__background-wrapper\"},[_c('div',{staticClass:\"watch-player__background\",class:{'watch-player__background--display': _vm.is_background_display},style:({backgroundImage: `url(${_vm.background_url})`})},[_c('img',{staticClass:\"watch-player__background-logo\",attrs:{\"src\":\"/assets/images/logo.svg\"}})])]),_c('v-progress-circular',{staticClass:\"watch-player__buffering\",class:{'watch-player__buffering--display': _vm.is_video_buffering && (_vm.is_loading || (_vm.player !== null && !_vm.player.video.paused))},attrs:{\"indeterminate\":\"\",\"size\":\"60\",\"width\":\"6\"}}),_c('div',{staticClass:\"watch-player__dplayer\"}),_c('div',{staticClass:\"watch-player__button\",on:{\"mousemove\":function($event){return _vm.controlDisplayTimer($event)},\"touchmove\":function($event){return _vm.controlDisplayTimer($event)},\"click\":function($event){return _vm.controlDisplayTimer($event)}}},[_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.top\",value:('前のチャンネル'),expression:\"'前のチャンネル'\",modifiers:{\"top\":true}}],staticClass:\"switch-button switch-button-up\",on:{\"click\":function($event){_vm.is_zapping = true; _vm.$router.push({path: `/tv/watch/${_vm.channelsStore.channel.previous.channel_id}`})}}},[_c('Icon',{staticClass:\"switch-button-icon\",attrs:{\"icon\":\"fluent:ios-arrow-left-24-filled\",\"width\":\"32px\",\"rotate\":\"1\"}})],1),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"switch-button switch-button-panel switch-button-panel--open\",on:{\"click\":function($event){_vm.is_panel_display = !_vm.is_panel_display}}},[_c('Icon',{staticClass:\"switch-button-icon\",attrs:{\"icon\":\"fluent:navigation-16-filled\",\"width\":\"32px\"}})],1),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"},{name:\"tooltip\",rawName:\"v-tooltip.bottom\",value:('次のチャンネル'),expression:\"'次のチャンネル'\",modifiers:{\"bottom\":true}}],staticClass:\"switch-button switch-button-down\",on:{\"click\":function($event){_vm.is_zapping = true; _vm.$router.push({path: `/tv/watch/${_vm.channelsStore.channel.next.channel_id}`})}}},[_c('Icon',{staticClass:\"switch-button-icon\",attrs:{\"icon\":\"fluent:ios-arrow-right-24-filled\",\"width\":\"33px\",\"rotate\":\"1\"}})],1)])],1)]),_c('div',{staticClass:\"watch-panel\",on:{\"mousemove\":function($event){return _vm.controlDisplayTimer($event)}}},[_c('div',{staticClass:\"watch-panel__header\"},[_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"panel-close-button\",on:{\"click\":function($event){_vm.is_panel_display = false}}},[_c('Icon',{staticClass:\"panel-close-button__icon\",attrs:{\"icon\":\"akar-icons:chevron-right\",\"width\":\"25px\"}}),_c('span',{staticClass:\"panel-close-button__text\"},[_vm._v(\"閉じる\")])],1),_c('v-spacer'),_c('div',{staticClass:\"panel-broadcaster\"},[_c('img',{staticClass:\"panel-broadcaster__icon\",attrs:{\"src\":`${_vm.Utils.api_base_url}/channels/${(_vm.channelsStore.channel_id)}/logo`}}),_c('div',{staticClass:\"panel-broadcaster__number\"},[_vm._v(_vm._s(_vm.channelsStore.channel.current.channel_number))]),_c('div',{staticClass:\"panel-broadcaster__name\"},[_vm._v(_vm._s(_vm.channelsStore.channel.current.channel_name))])])],1),_c('div',{staticClass:\"watch-panel__content-container\"},[_c('Program',{staticClass:\"watch-panel__content\",class:{'watch-panel__content--active': _vm.tv_panel_active_tab === 'Program'}}),_c('Channel',{staticClass:\"watch-panel__content\",class:{'watch-panel__content--active': _vm.tv_panel_active_tab === 'Channel'}}),_c('Comment',{ref:\"Comment\",staticClass:\"watch-panel__content\",class:{'watch-panel__content--active': _vm.tv_panel_active_tab === 'Comment'},attrs:{\"channel\":_vm.channelsStore.channel.current,\"player\":_vm.player}}),_c('Twitter',{ref:\"Twitter\",staticClass:\"watch-panel__content\",class:{'watch-panel__content--active': _vm.tv_panel_active_tab === 'Twitter'},attrs:{\"player\":_vm.player,\"is_virtual_keyboard_display\":_vm.is_virtual_keyboard_display},on:{\"panel_folding_requested\":function($event){_vm.is_panel_display = false}}})],1),_c('div',{staticClass:\"watch-panel__navigation\"},[_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"panel-navigation-button\",class:{'panel-navigation-button--active': _vm.tv_panel_active_tab === 'Program'},on:{\"click\":function($event){_vm.tv_panel_active_tab = 'Program'}}},[_c('Icon',{staticClass:\"panel-navigation-button__icon\",attrs:{\"icon\":\"fa-solid:info-circle\",\"width\":\"33px\"}}),_c('span',{staticClass:\"panel-navigation-button__text\"},[_vm._v(\"番組情報\")])],1),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"panel-navigation-button\",class:{'panel-navigation-button--active': _vm.tv_panel_active_tab === 'Channel'},on:{\"click\":function($event){_vm.tv_panel_active_tab = 'Channel'}}},[_c('Icon',{staticClass:\"panel-navigation-button__icon\",attrs:{\"icon\":\"fa-solid:broadcast-tower\",\"width\":\"34px\"}}),_c('span',{staticClass:\"panel-navigation-button__text\"},[_vm._v(\"チャンネル\")])],1),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"panel-navigation-button\",class:{'panel-navigation-button--active': _vm.tv_panel_active_tab === 'Comment'},on:{\"click\":function($event){_vm.tv_panel_active_tab = 'Comment'}}},[_c('Icon',{staticClass:\"panel-navigation-button__icon\",attrs:{\"icon\":\"bi:chat-left-text-fill\",\"width\":\"29px\"}}),_c('span',{staticClass:\"panel-navigation-button__text\"},[_vm._v(\"コメント\")])],1),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"panel-navigation-button\",class:{'panel-navigation-button--active': _vm.tv_panel_active_tab === 'Twitter'},on:{\"click\":function($event){_vm.tv_panel_active_tab = 'Twitter'}}},[_c('Icon',{staticClass:\"panel-navigation-button__icon\",attrs:{\"icon\":\"fa-brands:twitter\",\"width\":\"34px\"}}),_c('span',{staticClass:\"panel-navigation-button__text\"},[_vm._v(\"Twitter\")])],1)])])]),_c('v-dialog',{attrs:{\"max-width\":\"1050\",\"transition\":\"slide-y-transition\"},model:{value:(_vm.shortcut_key_modal),callback:function ($$v) {_vm.shortcut_key_modal=$$v},expression:\"shortcut_key_modal\"}},[_c('v-card',[_c('v-card-title',{staticClass:\"px-5 pt-4 pb-3 d-flex align-center font-weight-bold\"},[_c('Icon',{attrs:{\"icon\":\"fluent:keyboard-20-filled\",\"height\":\"28px\"}}),_c('span',{staticClass:\"ml-3\"},[_vm._v(\"キーボードショートカット\")]),_c('v-spacer'),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"d-flex align-center rounded-circle cursor-pointer px-2 py-2\",on:{\"click\":function($event){_vm.shortcut_key_modal = false}}},[_c('Icon',{attrs:{\"icon\":\"fluent:dismiss-12-filled\",\"width\":\"23px\",\"height\":\"23px\"}})],1)],1),_c('div',{staticClass:\"px-5 pb-4\"},[_c('v-row',_vm._l((_vm.shortcut_key_list),function(shortcut_key_column,shortcut_key_column_name){return _c('v-col',{key:shortcut_key_column_name,attrs:{\"cols\":\"6\"}},_vm._l((shortcut_key_column),function(shortcut_keys){return _c('div',{key:shortcut_keys.name,staticClass:\"mt-3\"},[_c('div',{staticClass:\"text-subtitle-1 d-flex align-center font-weight-bold\"},[_c('Icon',{attrs:{\"icon\":shortcut_keys.icon,\"height\":shortcut_keys.icon_height}}),_c('span',{staticClass:\"ml-2\"},[_vm._v(_vm._s(shortcut_keys.name))])],1),_vm._l((shortcut_keys.shortcuts),function(shortcut){return _c('div',{key:shortcut.name,staticClass:\"mt-3\"},[_c('div',{staticClass:\"text-subtitle-2 mt-2 d-flex align-center font-weight-medium\"},[_c('span',{staticClass:\"mr-2\",domProps:{\"innerHTML\":_vm._s(shortcut.name)}}),_c('div',{staticClass:\"ml-auto d-flex align-center flex-shrink-0\"},_vm._l((shortcut.keys),function(key,index){return _c('div',{key:key.name,staticClass:\"ml-auto d-flex align-center\"},[_c('span',{staticClass:\"shortcut-key\"},[_vm._l((key.name.split(';')),function(key_name){return _c('Icon',{directives:[{name:\"show\",rawName:\"v-show\",value:(key.icon === true),expression:\"key.icon === true\"}],key:key_name,attrs:{\"icon\":key_name,\"height\":\"18px\"}})}),(key.icon === false)?_c('span',{domProps:{\"innerHTML\":_vm._s(key.name)}}):_vm._e()],2),(index < (shortcut.keys.length - 1))?_c('span',{staticClass:\"shortcut-key-plus\"},[_vm._v(\"+\")]):_vm._e()])}),0)])])})],2)}),0)}),1)],1)],1)],1),_c('BottomNavigation')],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"channels-container channels-container--watch\"},[_c('v-tabs-fix',{staticClass:\"channels-tab\",attrs:{\"centered\":\"\",\"show-arrows\":\"\"},model:{value:(_vm.tab),callback:function ($$v) {_vm.tab=$$v},expression:\"tab\"}},_vm._l((Array.from(_vm.channelsStore.channels_list_with_pinned_for_watch)),function([channels_type,]){return _c('v-tab',{key:channels_type,staticClass:\"channels-tab__item\"},[_vm._v(\" \"+_vm._s(channels_type)+\" \")])}),1),_c('div',{staticClass:\"channels-list-container\"},[_c('v-tabs-items-fix',{staticClass:\"channels-list\",model:{value:(_vm.tab),callback:function ($$v) {_vm.tab=$$v},expression:\"tab\"}},_vm._l((Array.from(_vm.channelsStore.channels_list_with_pinned_for_watch)),function([channels_type, channels]){return _c('v-tab-item-fix',{key:channels_type,staticClass:\"channels\"},_vm._l((channels),function(channel){return _c('router-link',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],key:channel.id,staticClass:\"channel\",attrs:{\"to\":`/tv/watch/${channel.channel_id}`}},[_c('div',{staticClass:\"channel__broadcaster\"},[_c('img',{staticClass:\"channel__broadcaster-icon\",attrs:{\"src\":`${_vm.Utils.api_base_url}/channels/${channel.channel_id}/logo`}}),_c('div',{staticClass:\"channel__broadcaster-content\"},[_c('span',{staticClass:\"channel__broadcaster-name\"},[_vm._v(\"Ch: \"+_vm._s(channel.channel_number)+\" \"+_vm._s(channel.channel_name))]),_c('div',{staticClass:\"channel__broadcaster-force\",class:`channel__broadcaster-force--${_vm.ChannelUtils.getChannelForceType(channel.channel_force)}`},[_c('Icon',{attrs:{\"icon\":\"fa-solid:fire-alt\",\"height\":\"11px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(_vm._s(_vm.ProgramUtils.getAttribute(channel, 'channel_force', '-')))])],1)])]),_c('div',{staticClass:\"channel__program-present\"},[_c('span',{staticClass:\"channel__program-present-title\",domProps:{\"innerHTML\":_vm._s(_vm.ProgramUtils.decorateProgramInfo(channel.program_present, 'title'))}}),_c('span',{staticClass:\"channel__program-present-time\"},[_vm._v(_vm._s(_vm.ProgramUtils.getProgramTime(channel.program_present)))])]),_c('div',{staticClass:\"channel__program-following\"},[_c('div',{staticClass:\"channel__program-following-title\"},[_c('span',{staticClass:\"channel__program-following-title-decorate\"},[_vm._v(\"NEXT\")]),_c('Icon',{staticClass:\"channel__program-following-title-icon\",attrs:{\"icon\":\"fluent:fast-forward-20-filled\",\"width\":\"16px\"}}),_c('span',{staticClass:\"channel__program-following-title-text\",domProps:{\"innerHTML\":_vm._s(_vm.ProgramUtils.decorateProgramInfo(channel.program_following, 'title'))}})],1),_c('span',{staticClass:\"channel__program-following-time\"},[_vm._v(_vm._s(_vm.ProgramUtils.getProgramTime(channel.program_following)))])]),_c('div',{staticClass:\"channel__progressbar\"},[_c('div',{staticClass:\"channel__progressbar-progress\",style:(`width:${_vm.ProgramUtils.getProgramProgress(channel.program_present)}%;`)})])])}),1)}),1)],1)],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport useChannelsStore from '@/store/ChannelsStore';\nimport Utils, { ChannelUtils, ProgramUtils } from '@/utils';\n\nexport default Vue.extend({\n name: 'Panel-ChannelTab',\n data() {\n return {\n\n // ユーティリティをテンプレートで使えるように\n Utils: Utils,\n ChannelUtils: ChannelUtils,\n ProgramUtils: ProgramUtils,\n\n // タブの状態管理\n tab: null,\n };\n },\n computed: {\n // ChannelsStore に this.channelsStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useChannelsStore),\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Channel.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Channel.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Channel.vue?vue&type=template&id=3b3e1928&scoped=true&\"\nimport script from \"./Channel.vue?vue&type=script&lang=ts&\"\nexport * from \"./Channel.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Channel.vue?vue&type=style&index=0&id=3b3e1928&prod&lang=scss&\"\nimport style1 from \"./Channel.vue?vue&type=style&index=1&id=3b3e1928&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"3b3e1928\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"comment-container\"},[_c('section',{staticClass:\"comment-header\"},[_c('h2',{staticClass:\"comment-header__title\"},[_c('Icon',{staticClass:\"comment-header__title-icon\",attrs:{\"icon\":\"bi:chat-left-text-fill\",\"height\":\"18.5px\"}}),_c('span',{staticClass:\"comment-header__title-text\"},[_vm._v(\"コメント\")])],1),_c('button',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"comment-header__button ml-auto\",on:{\"click\":function($event){_vm.comment_mute_settings_modal = !_vm.comment_mute_settings_modal}}},[_c('Icon',{attrs:{\"icon\":\"heroicons-solid:filter\",\"height\":\"11px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(\"ミュート設定\")])],1)]),_c('section',{ref:\"comment_list_wrapper\",staticClass:\"comment-list-wrapper\"},[_c('div',{staticClass:\"comment-list-dropdown\",class:{'comment-list-dropdown--display': _vm.is_comment_list_dropdown_display},style:({'--comment-list-dropdown-top': `${_vm.comment_list_dropdown_top}px`})},[_c('v-list',{staticStyle:{\"background\":\"var(--v-background-lighten1)\"}},[_c('v-list-item',{staticStyle:{\"min-height\":\"30px\"},attrs:{\"dense\":\"\"},on:{\"click\":function($event){return _vm.addMutedKeywords()}}},[_c('v-list-item-title',{staticClass:\"d-flex align-center\"},[_c('Icon',{attrs:{\"icon\":\"fluent:comment-dismiss-20-filled\",\"width\":\"20px\"}}),_c('span',{staticClass:\"ml-2\"},[_vm._v(\"このコメントをミュート\")])],1)],1),_c('v-list-item',{staticStyle:{\"min-height\":\"30px\"},attrs:{\"dense\":\"\"},on:{\"click\":function($event){return _vm.addMutedNiconicoUserIds()}}},[_c('v-list-item-title',{staticClass:\"d-flex align-center\"},[_c('Icon',{attrs:{\"icon\":\"fluent:person-prohibited-20-filled\",\"width\":\"20px\"}}),_c('span',{staticClass:\"ml-2\"},[_vm._v(\"このコメントの投稿者をミュート\")])],1)],1)],1)],1),_c('div',{staticClass:\"comment-list-cover\",class:{'comment-list-cover--display': _vm.is_comment_list_dropdown_display},on:{\"click\":function($event){return _vm.hideCommentListDropdown()}}}),_c('DynamicScroller',{staticClass:\"comment-list\",attrs:{\"direction\":'vertical',\"items\":_vm.comment_list,\"min-item-size\":34},scopedSlots:_vm._u([{key:\"default\",fn:function({item, active}){return [_c('DynamicScrollerItem',{attrs:{\"item\":item,\"active\":active,\"size-dependencies\":[item.text]}},[_c('div',{staticClass:\"comment\",class:{'comment--my-post': item.my_post}},[_c('span',{staticClass:\"comment__text\"},[_vm._v(_vm._s(item.text))]),_c('span',{staticClass:\"comment__time\"},[_vm._v(_vm._s(item.time))]),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\",value:(!_vm.Utils.isTouchDevice()),expression:\"!Utils.isTouchDevice()\"}],staticClass:\"comment__icon\",on:{\"mouseup\":function($event){return _vm.showCommentListDropdown($event, item)},\"touchend\":function($event){return _vm.showCommentListDropdown($event, item)}}},[_c('Icon',{attrs:{\"icon\":\"fluent:more-vertical-20-filled\",\"width\":\"20px\"}})],1)])])]}}])}),(_vm.initialize_failed_message === null && _vm.comment_list.length === 0)?_c('div',{staticClass:\"comment-announce\"},[_c('div',{staticClass:\"comment-announce__heading\"},[_vm._v(\"まだコメントがありません。\")]),_vm._m(0)]):_vm._e(),(_vm.initialize_failed_message !== null && _vm.comment_list.length === 0)?_c('div',{staticClass:\"comment-announce\"},[_c('div',{staticClass:\"comment-announce__heading\"},[_vm._v(\"コメントがありません。\")]),_c('div',{staticClass:\"comment-announce__text\"},[_c('p',{staticClass:\"mt-0 mb-0\"},[_vm._v(_vm._s(_vm.initialize_failed_message))])])]):_vm._e()],1),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"comment-scroll-button elevation-5\",class:{'comment-scroll-button--display': _vm.is_manual_scroll},on:{\"click\":function($event){_vm.is_manual_scroll = false; _vm.scrollCommentList(true);}}},[_c('Icon',{attrs:{\"icon\":\"fluent:arrow-down-12-filled\",\"height\":\"29px\"}})],1),_c('CommentMuteSettings',{model:{value:(_vm.comment_mute_settings_modal),callback:function ($$v) {_vm.comment_mute_settings_modal=$$v},expression:\"comment_mute_settings_modal\"}})],1)\n}\nvar staticRenderFns = [function (){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"comment-announce__text\"},[_c('p',{staticClass:\"mt-0 mb-0\"},[_vm._v(\"このチャンネルに対応するニコニコ実況のコメントが、リアルタイムで表示されます。\")])])\n}]\n\nexport { render, staticRenderFns }","\nimport dayjs from 'dayjs';\nimport DPlayer from 'dplayer';\nimport * as DPlayerType from 'dplayer/dist/d.ts/types/DPlayer';\n\nimport Channels from '@/services/Channels';\nimport Utils, { CommentUtils } from '@/utils';\n\n\nexport interface ICommentData {\n id: number;\n text: string;\n time: string;\n user_id: string;\n my_post: boolean;\n}\n\ninterface IWatchSessionResult {\n is_success: boolean;\n detail: string;\n message_server_url?: string;\n thread_id?: string;\n your_post_key?: string;\n}\n\n\nclass LiveCommentManager {\n\n // 視聴セッションの WebSocket のインスタンス\n private watch_session: WebSocket | null = null;\n // コメントセッションの WebSocket のインスタンス\n private comment_session: WebSocket | null = null;\n // vpos を計算する基準となる時刻のタイムスタンプ\n private vpos_base_timestamp: number = 0;\n // 座席維持用のタイマーのインターバル ID\n private keep_seat_interval_id: number | null = null;\n // destroy() 時に EventListener を全解除するための AbortController\n private abort_controller: AbortController = new AbortController();\n\n private player: DPlayer;\n private channel_id: string;\n private on_initial_comments_received: (initial_comments: ICommentData[]) => void;\n private on_comment_received: (comment: ICommentData) => void;\n\n constructor(\n player: DPlayer,\n channel_id: string,\n on_initial_comments_received: (initial_comments: ICommentData[]) => void,\n on_comment_received: (comment: ICommentData) => void,\n ) {\n this.player = player;\n this.channel_id = channel_id;\n this.on_initial_comments_received = on_initial_comments_received;\n this.on_comment_received = on_comment_received;\n }\n\n\n /**\n * ニコニコ実況に接続し、セッションを初期化する\n * 初期化に成功した場合は、随時コールバックにニコニコ実況から受信したコメントが渡される\n * @returns セッションの初期化に成功したかどうか\n */\n public async initSession(): Promise<{\n is_success: boolean;\n detail: string;\n }> {\n\n // 視聴セッションを初期化\n const watch_session_result = await this.initWatchSession();\n if (watch_session_result.is_success === false) {\n return {\n is_success: false,\n detail: watch_session_result.detail,\n };\n }\n\n // 視聴セッションを初期化できた場合のみ、\n // 取得したコメントサーバーへの接続情報を使い、非同期でコメントセッションを初期化\n this.initCommentSession(watch_session_result);\n\n return {\n is_success: true,\n detail: '視聴セッションを初期化しました。',\n };\n }\n\n\n /**\n * 視聴セッションを初期化する\n * @returns コメントサーバーへの接続情報 or エラー情報\n */\n private async initWatchSession(): Promise {\n\n // サーバーから disconnect メッセージが送られてきた際のフラグ\n let is_disconnect_message_received = false;\n\n // セッション情報を取得\n const watch_session_info = await Channels.fetchJikkyoSession(this.channel_id);\n if (watch_session_info === null) {\n return {\n is_success: false,\n detail: 'ニコニコ実況のセッション情報を取得できませんでした。',\n };\n }\n if (watch_session_info.is_success === false) {\n console.error(`[LiveCommentManager][WatchSession] Error: ${watch_session_info.detail}`);\n // 通常発生しないエラーメッセージ (サーバーエラーなど) はプレイヤー側にも通知する\n if ((watch_session_info.detail !== 'このチャンネルはニコニコ実況に対応していません。') &&\n (watch_session_info.detail !== '現在放送中のニコニコ実況がありません。')) {\n this.player.notice(watch_session_info.detail);\n }\n return {\n is_success: false,\n detail: watch_session_info.detail,\n };\n }\n\n // 視聴セッション WebSocket を開く\n this.watch_session = new WebSocket(watch_session_info.audience_token);\n\n // 視聴セッションの接続が開かれたとき\n this.watch_session.addEventListener('open', () => {\n\n // 視聴セッションをリクエスト\n // 公式ドキュメントいわく、stream フィールドは Optional らしい\n // サーバー負荷軽減のため、映像が不要な場合は必ず省略してくださいとのこと\n this.watch_session?.send(JSON.stringify({\n type: 'startWatching',\n data: {\n 'reconnect': false,\n },\n }));\n\n }, { signal: this.abort_controller.signal });\n\n // 視聴セッションの接続が閉じられたとき(ネットワークが切断された場合など)\n this.watch_session.addEventListener('close', async (event) => {\n\n // すでに disconnect メッセージが送られてきている場合は何もしない\n if (is_disconnect_message_received === true) {\n return;\n }\n\n // 接続切断の理由を表示\n console.error(`[LiveCommentManager][WatchSession] Connection closed. (Code: ${event.code})`);\n this.player.notice(`ニコニコ実況との接続が切断されました。(Code: ${event.code})`);\n\n // 10 秒ほど待ってから再接続する\n // ニコ生側から切断された場合と異なりネットワークが切断された可能性が高いので、間を多めに取る\n await Utils.sleep(10);\n await this.reconnect();\n\n }, { signal: this.abort_controller.signal });\n\n // 視聴セッション WebSocket からメッセージを受信したとき\n // 視聴セッションはコメント送信時のために維持し続ける必要がある\n // 以下はいずれも視聴セッションを維持し続けたり、エラーが発生した際に再接続するための処理\n this.watch_session.addEventListener('message', async (event) => {\n\n // 各メッセージタイプに対応する処理を実行\n const message = JSON.parse(event.data);\n switch (message.type) {\n\n // 座席情報\n case 'seat': {\n // すでにタイマーが設定されている場合は何もしない\n if (this.keep_seat_interval_id !== null) {\n break;\n }\n // keepIntervalSec の秒数ごとに keepSeat を送信して座席を維持する\n this.keep_seat_interval_id = window.setInterval(() => {\n if (this.watch_session && this.watch_session.readyState === WebSocket.OPEN) {\n // セッションがまだ開いていれば、座席を維持する\n this.watch_session.send(JSON.stringify({type: 'keepSeat'}));\n } else {\n // セッションが閉じられている場合は、タイマーを停止する\n window.clearInterval(this.keep_seat_interval_id ?? 0);\n }\n }, message.data.keepIntervalSec * 1000);\n break;\n }\n\n // ping-pong\n case 'ping': {\n // pong を返してセッションを維持する\n // 送り返さなかった場合、勝手にセッションが閉じられてしまう\n this.watch_session.send(JSON.stringify({type: 'pong'}));\n break;\n }\n\n // エラー情報\n case 'error': {\n // COMMENT_POST_NOT_ALLOWED と INVALID_MESSAGE に関しては sendComment() の方で処理するので、ここでは何もしない\n if (message.data.code === 'COMMENT_POST_NOT_ALLOWED' || message.data.code === 'INVALID_MESSAGE') {\n break;\n }\n\n let error = `ニコニコ実況でエラーが発生しています。(Code: ${message.data.code})`;\n switch (message.data.code) {\n case 'CONNECT_ERROR':\n error = 'ニコニコ実況のコメントサーバーに接続できません。';\n break;\n case 'CONTENT_NOT_READY':\n error = 'ニコニコ実況が配信できない状態です。';\n break;\n case 'NO_THREAD_AVAILABLE':\n error = 'ニコニコ実況のコメントスレッドを取得できません。';\n break;\n case 'NO_ROOM_AVAILABLE':\n error = 'ニコニコ実況のコメント部屋を取得できません。';\n break;\n case 'NO_PERMISSION':\n error = 'ニコニコ実況の API にアクセスする権限がありません。';\n break;\n case 'NOT_ON_AIR':\n error = 'ニコニコ実況が放送中ではありません。';\n break;\n case 'BROADCAST_NOT_FOUND':\n error = 'ニコニコ実況の配信情報を取得できません。';\n break;\n case 'INTERNAL_SERVERERROR':\n error = 'ニコニコ実況でサーバーエラーが発生しています。';\n break;\n }\n\n // エラー情報を表示\n console.error(`[LiveCommentManager][WatchSession] Error occurred. (Code: ${message.data.code})`);\n this.player.notice(error);\n\n // 5 秒ほど待ってから再接続する\n await Utils.sleep(5);\n await this.reconnect();\n break;\n }\n\n // 再接続を求められた\n case 'reconnect': {\n // waitTimeSec に記載の秒数だけ待ってから再接続する\n // 公式ドキュメントには reconnect で送られてくる audienceToken で再接続しろと書いてあるんだけど、\n // 確実性的な面で実装が面倒なので当面このままにしておく\n await this.reconnect();\n break;\n }\n\n // 視聴セッションが閉じられた(4時のリセットなど)\n case 'disconnect': {\n // 実際に接続が閉じられる前に disconnect メッセージが送られてきたので、\n // WebSocket の close メッセージを実行させないようにする\n is_disconnect_message_received = true;\n\n // 接続切断の理由\n let disconnect_reason = `ニコニコ実況との接続が切断されました。(${message.data.reason})`;\n switch (message.data.reason) {\n case 'TAKEOVER':\n disconnect_reason = 'ニコニコ実況の番組から追い出されました。';\n break;\n case 'NO_PERMISSION':\n disconnect_reason = 'ニコニコ実況の番組の座席を取得できませんでした。';\n break;\n case 'END_PROGRAM':\n disconnect_reason = 'ニコニコ実況がリセットされたか、コミュニティの番組が終了しました。';\n break;\n case 'PING_TIMEOUT':\n disconnect_reason = 'コメントサーバーとの接続生存確認に失敗しました。';\n break;\n case 'TOO_MANY_CONNECTIONS':\n disconnect_reason = 'ニコニコ実況の同一ユーザからの接続数上限を越えています。';\n break;\n case 'TOO_MANY_WATCHINGS':\n disconnect_reason = 'ニコニコ実況の同一ユーザからの視聴番組数上限を越えています。';\n break;\n case 'CROWDED':\n disconnect_reason = 'ニコニコ実況の番組が満席です。';\n break;\n case 'MAINTENANCE_IN':\n disconnect_reason = 'ニコニコ実況はメンテナンス中です。';\n break;\n case 'SERVICE_TEMPORARILY_UNAVAILABLE':\n disconnect_reason = 'ニコニコ実況で一時的にサーバーエラーが発生しています。';\n break;\n }\n\n // 接続切断の理由を表示\n console.error(`[LiveCommentManager][WatchSession] Disconnected. (Reason: ${message.data.reason})`);\n this.player.notice(disconnect_reason);\n\n // 5 秒ほど待ってから再接続する\n await Utils.sleep(5);\n await this.reconnect();\n break;\n }\n }\n\n }, { signal: this.abort_controller.signal });\n\n // コメントサーバーへの接続情報を返す\n // イベント内で値を返すため、Promise で包む\n return new Promise((resolve) => {\n this.watch_session.addEventListener('message', async (event) => {\n const message = JSON.parse(event.data);\n if (message.type === 'room') {\n\n // vpos の基準時刻のタイムスタンプを取得 (ミリ秒単位)\n // vpos は番組開始時間からの累計秒数\n this.vpos_base_timestamp = dayjs(message.data.vposBaseTime).valueOf();\n\n // コメントサーバーへの接続情報を返す\n console.log(`[LiveCommentManager][WatchSession] Connected.\\nThread ID: ${message.data.threadId}\\n`);\n return resolve({\n is_success: true,\n detail: '視聴セッションを取得しました。',\n // コメントサーバーへの接続情報\n message_server_url: message.data.messageServer.uri,\n // コメントサーバー上のスレッド ID\n thread_id: message.data.threadId,\n // メッセージサーバーから受信するコメント (chat メッセージ) に yourpost フラグを付けるためのキー\n your_post_key: (message.data.yourPostKey ? message.data.yourPostKey : null),\n });\n }\n }, { signal: this.abort_controller.signal });\n });\n }\n\n\n /**\n * コメントセッションを初期化する\n * @param comment_session_info コメントサーバーへの接続情報\n */\n private initCommentSession(comment_session_info: IWatchSessionResult): void {\n\n // 初回にドカッと送信されてくる過去コメントを受信し終えるまで格納するバッファ\n const initial_comments_buffer: ICommentData[] = [];\n\n // 初回にドカッと送信されてくる過去コメントを受信し終えたかどうかのフラグ\n let initial_comments_received = false;\n\n // コメントセッション WebSocket を開く\n this.comment_session = new WebSocket(comment_session_info.message_server_url);\n\n // コメントセッション WebSocket を開いたとき\n this.comment_session.addEventListener('open', () => {\n\n // コメント送信をリクエスト\n // このコマンドを送らないとコメントが送信されてこない\n this.comment_session.send(JSON.stringify([\n {ping: {content: 'rs:0'}},\n {ping: {content: 'ps:0'}},\n {\n thread: {\n version: '20061206', // 設定必須\n thread: comment_session_info.thread_id, // スレッド ID\n threadkey: comment_session_info.your_post_key, // スレッドキー\n user_id: '', // ユーザー ID(設定不要らしい)\n res_from: -50, // 最初にコメントを 50 個送信する\n }\n },\n {ping: {content: 'pf:0'}},\n {ping: {content: 'rf:0'}},\n ]));\n\n }, { signal: this.abort_controller.signal });\n\n // コメントセッション WebSocket からメッセージを受信したとき\n this.comment_session.addEventListener('message', async (event) => {\n\n // メッセージを取得\n const message = JSON.parse(event.data);\n\n // 接続失敗\n if (message.thread !== undefined) {\n if (message.thread.resultcode !== 0) {\n console.error(`[LiveCommentManager][CommentSession] Connection failed. (Code: ${message.thread.resultcode})`);\n return;\n }\n }\n\n // ping メッセージのみ\n // rf:0 が送られてきたら初回にドカッと送信されてくる過去コメントの受信は完了\n // この時点で初回コメントを一気にコールバックに送る\n if (message.ping !== undefined && message.ping.content === 'rf:0') {\n initial_comments_received = true;\n this.on_initial_comments_received(initial_comments_buffer);\n return;\n }\n\n // コメントデータを取得\n const comment = message.chat;\n\n // コメントデータが不正な場合 or 自分のコメントの場合は弾く\n if ((comment === undefined || comment.content === undefined || comment.content === '') ||\n (comment.yourpost && comment.yourpost === 1)) {\n return;\n }\n\n // コメントコマンドをパース\n const { color, position, size } = CommentUtils.parseCommentCommand(comment.mail);\n\n // ミュート対象のコメントかどうかを判定し、もしそうならここで弾く\n if (CommentUtils.isMutedComment(comment.content, comment.user_id, color, position, size)) {\n return;\n }\n\n // コメントリストへ追加するオブジェクト\n const comment_data: ICommentData = {\n id: comment.no,\n text: comment.content,\n time: dayjs(comment.date * 1000).format('HH:mm:ss'),\n user_id: comment.user_id,\n my_post: false,\n };\n\n // もしまだ初回コメントを受信し終えていないなら、バッファに格納して終了\n // 初回コメントはプレイヤーには描画しないため問題ない\n if (initial_comments_received === false) {\n initial_comments_buffer.push(comment_data);\n return;\n }\n\n // 配信で発生する遅延分待ってから\n // おおよその遅延時間は video.buffered.end(0) - video.currentTime で取得できる\n let buffered_end = 0;\n if (this.player.video.buffered.length >= 1) {\n buffered_end = this.player.video.buffered.end(0);\n }\n const comment_delay_time = buffered_end - this.player.video.currentTime;\n // console.log(`[LiveCommentManager][CommentSession] Delay: ${comment_delay_time} sec.`)\n await Utils.sleep(comment_delay_time);\n\n // コールバック関数を実行\n this.on_comment_received(comment_data);\n\n // プレイヤーにコメントを描画する (映像再生時のみ)\n if (this.player.video.paused === false) {\n this.player.danmaku.draw({\n text: comment.content,\n color: color,\n type: position,\n size: size,\n });\n }\n\n }, { signal: this.abort_controller.signal });\n }\n\n\n /**\n * ニコニコ実況にコメントを送信する\n * @param options DPlayer のコメントオプション\n */\n public sendComment(options: DPlayerType.APIBackendSendOptions): void {\n\n // DPlayer 上のコメント色(カラーコード)とニコニコの色コマンド定義のマッピング\n const color_table = {\n '#FFEAEA': 'white',\n '#F02840': 'red',\n '#FD7E80': 'pink',\n '#FDA708': 'orange',\n '#FFE133': 'yellow',\n '#64DD17': 'green',\n '#00D4F5': 'cyan',\n '#4763FF': 'blue',\n };\n\n // DPlayer 上のコメント位置を表す値とニコニコの位置コマンド定義のマッピング\n const position_table = {\n 'top': 'ue',\n 'right': 'naka',\n 'bottom': 'shita',\n };\n\n // vpos を計算 (10ミリ秒単位)\n // 番組開始時間からの累計秒らしいけど、なぜ指定しないといけないのかは不明\n // 小数点以下は丸めないとコメントサーバー側で投稿エラーになる\n const vpos = Math.round((dayjs().valueOf() - this.vpos_base_timestamp) / 10);\n\n // 視聴セッションが null か、接続が切れている場合は弾く\n if (this.watch_session === null || this.watch_session.readyState !== WebSocket.OPEN) {\n console.error('[LiveCommentManager][WatchSession] Comment sending failed. (Connection is not established.)');\n options.error('コメントの送信に失敗しました。WebSocket 接続が確立されていません。');\n return;\n }\n\n // コメントを送信\n this.watch_session.send(JSON.stringify({\n 'type': 'postComment',\n 'data': {\n // コメント本文\n 'text': options.data.text,\n // コメントの色\n 'color': color_table[options.data.color.toUpperCase()],\n // コメント位置\n 'position': position_table[options.data.type],\n // コメントサイズ (DPlayer とニコニコで表現が共通なため、変換不要)\n 'size': options.data.size,\n // 番組開始時間からの累計秒 (10ミリ秒単位)\n 'vpos': vpos,\n // 匿名コメント (184) にするかどうか\n 'isAnonymous': true,\n }\n }));\n\n // コメント送信のレスポンスを取得\n const abort_controller = new AbortController();\n this.watch_session.addEventListener('message', (event) => {\n const message = JSON.parse(event.data);\n switch (message.type) {\n\n // postCommentResult が送られてきた → コメント送信に成功している\n case 'postCommentResult': {\n // コメント成功を DPlayer にコールバックで通知\n options.success();\n\n // イベントリスナーを削除\n abort_controller.abort();\n break;\n }\n\n // コメント送信直後に error が送られてきた → コメント送信に失敗している\n case 'error': {\n // コメント失敗を DPlayer にコールバックで通知\n let error = `コメントの送信に失敗しました。(${message.data.code})`;\n switch (message.data.code) {\n case 'COMMENT_POST_NOT_ALLOWED':\n error = 'コメントが許可されていません。';\n break;\n case 'INVALID_MESSAGE':\n error = 'コメント内容が無効です。';\n break;\n }\n console.error(`[LiveCommentManager][WatchSession] Comment sending failed. (Code: ${message.data.code})`);\n options.error(error);\n\n // イベントリスナーを解除\n abort_controller.abort();\n break;\n }\n }\n }, { signal: abort_controller.signal });\n }\n\n\n /**\n * 同じ設定でニコニコ実況に再接続する\n */\n private async reconnect(): Promise {\n console.warn('[LiveCommentManager][WatchSession] Reconnecting...');\n this.player.notice('ニコニコ実況に再接続しています…');\n\n // 前のセッションを破棄\n this.destroy();\n\n // セッションを再初期化\n const result = await this.initSession();\n if (result.is_success === false) {\n console.error('[LiveCommentManager][WatchSession] Reconnection failed.');\n this.player.notice(result.detail);\n }\n }\n\n\n /**\n * 視聴セッションとコメントセッションをそれぞれ閉じる\n */\n public destroy(): void {\n\n // セッションに紐いているすべての EventListener を解除\n // 再接続する場合に備えて AbortController を作り直す\n this.abort_controller.abort();\n this.abort_controller = new AbortController();\n\n // 視聴セッションを閉じる\n if (this.watch_session !== null) {\n this.watch_session.close(); // WebSocket を閉じる\n this.watch_session = null; // null に戻す\n }\n\n // コメントセッションを閉じる\n if (this.comment_session !== null) {\n this.comment_session.close(); // WebSocket を閉じる\n this.comment_session = null; // null に戻す\n }\n\n // 座席保持用のタイマーをクリア\n window.clearInterval(this.keep_seat_interval_id);\n this.keep_seat_interval_id = null;\n this.vpos_base_timestamp = 0;\n\n console.log('[LiveCommentManager][WatchSession] Destroyed.');\n }\n}\n\nexport default LiveCommentManager;\n","\n\nimport DPlayer from 'dplayer';\nimport * as DPlayerType from 'dplayer/dist/d.ts/types/DPlayer';\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport CommentMuteSettings from '@/components/Settings/CommentMuteSettings.vue';\nimport LiveCommentManager, { ICommentData } from '@/services/player/LiveCommentManager';\nimport useUserStore from '@/store/UserStore';\nimport Utils, { CommentUtils } from '@/utils';\n\nexport default Vue.extend({\n name: 'Panel-CommentTab',\n components: {\n CommentMuteSettings,\n },\n data() {\n return {\n\n // ユーティリティをテンプレートで使えるように\n Utils: Utils,\n\n // 手動スクロール状態かどうか\n is_manual_scroll: false,\n\n // 自動スクロール中かどうか\n // 自動スクロール中の場合、scroll イベントが発火しても無視する\n is_auto_scrolling: false,\n\n // コメントリストの配列\n comment_list: [] as ICommentData[],\n\n // コメントリストの要素\n comment_list_element: null as HTMLElement | null,\n\n // コメントリストのドロップダウン関連\n is_comment_list_dropdown_display: false as boolean,\n comment_list_dropdown_top: 0 as number,\n comment_list_dropdown_comment: null as ICommentData | null,\n\n // LiveCommentManager のインスタンス\n live_comment_manager: null as LiveCommentManager | null,\n\n // ニコニコ実況セッションの初期化に失敗した際のエラーメッセージ\n // 視聴中チャンネルのニコニコ実況がないときなどに発生する\n initialize_failed_message: null as string | null,\n\n // visibilitychange イベントのリスナー\n visibilitychange_listener: null as (() => void) | null,\n\n // ResizeObserver のインスタンス\n resize_observer: null as ResizeObserver | null,\n\n // ResizeObserver の監視対象の要素\n resize_observer_element: null as HTMLElement | null,\n\n // コメントのミュート設定のモーダルを表示するか\n comment_mute_settings_modal: false,\n };\n },\n computed: {\n // UserStore に this.userStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useUserStore),\n },\n created() {\n\n // アカウント情報を更新\n this.userStore.fetchUser();\n },\n mounted() {\n\n // コメントリストの要素を取得\n if (this.comment_list_element === null) {\n this.comment_list_element = this.$el.querySelector('.comment-list');\n }\n\n // 現在コメントリストがユーザーイベントでスクロールされているかどうか\n let is_user_scrolling = false;\n\n // mousedown → mouseup 中: スクロールバーをマウスでドラッグ\n // 残念ながらスクロールバーのドラッグ中は mousemove のイベントが発火しないため、直接 is_user_scrolling を設定する\n this.comment_list_element.onmousedown = (event: MouseEvent) => {\n // コメントリストの要素の左上を起点としたカーソルのX座標を求める\n const x = event.clientX - this.comment_list_element.getBoundingClientRect().left;\n // 座標が clientWidth 以上であれば、スクロールバー上で mousedown されたものとする\n if (x > this.comment_list_element.clientWidth) is_user_scrolling = true;\n };\n this.comment_list_element.onmouseup = (event: MouseEvent) => {\n // コメントリストの要素の左上を起点としたカーソルのX座標を求める\n const x = event.clientX - this.comment_list_element.getBoundingClientRect().left;\n // 座標が clientWidth 以上であれば、スクロールバー上で mouseup されたものとする\n if (x > this.comment_list_element.clientWidth) is_user_scrolling = false;\n };\n\n // ユーザーによるスクロールイベントで is_user_scrolling を true にする\n // 0.1 秒後に false にする(継続してイベントが発火すれば再び true になる)\n const on_user_scrolling = () => {\n is_user_scrolling = true;\n window.setTimeout(() => is_user_scrolling = false, 100);\n };\n\n // 現在コメントリストがドラッグされているかどうか\n let is_dragging = false;\n // touchstart → touchend 中: スクロールバーをタップでドラッグ\n this.comment_list_element.ontouchstart = () => is_dragging = true;\n this.comment_list_element.ontouchend = () => is_dragging = false;\n // touchmove + is_dragging 中: コメントリストをタップでドラッグしてスクロール\n this.comment_list_element.ontouchmove = () => is_dragging === true ? on_user_scrolling(): '';\n\n // wheel 中: マウスホイールの回転\n this.comment_list_element.onwheel = on_user_scrolling;\n\n // コメントリストがスクロールされた際、自動スクロール中でない&ユーザーイベントで操作されていれば、手動スクロールモードに設定\n // 手動スクロールモードでは自動スクロールを行わず、ユーザーがコメントリストをスクロールできるようにする\n this.comment_list_element.onscroll = async () => {\n\n // scroll イベントは自動スクロールでも発火してしまうので、ユーザーイベントによるスクロールかを確認しないといけない\n // 自動スクロール中かどうかは is_auto_scrolling が true のときで判定できるはずだが、\n // コメントが多くなると is_auto_scrolling が false なのに scroll イベントが遅れて発火してしまうことがある\n if (this.is_auto_scrolling === false && is_user_scrolling === true) {\n\n // 手動スクロールを有効化\n this.is_manual_scroll = true;\n\n // イベント発火時点では scrollTop の値が完全に下にスクロールされていない場合があるため、0.1秒だけ待つ\n await Utils.sleep(0.1);\n\n // 一番下までスクロールされていたら自動スクロールに戻す\n if ((this.comment_list_element.scrollTop + this.comment_list_element.offsetHeight) >\n (this.comment_list_element.scrollHeight - 10)) { // 一番下から 10px 以内\n this.is_manual_scroll = false; // 手動スクロールを無効化\n }\n }\n };\n },\n // 終了前に実行\n beforeDestroy() {\n\n // ニコニコ実況セッションを破棄\n this.destroy();\n\n // ResizeObserver を終了\n if (this.resize_observer !== null) {\n this.resize_observer.unobserve(this.resize_observer_element);\n }\n },\n methods: {\n\n // ドロップダウンメニューを表示する\n showCommentListDropdown(event: Event, comment: ICommentData) {\n const comment_list_wrapper_rect = (this.$refs.comment_list_wrapper as HTMLDivElement).getBoundingClientRect();\n const comment_list_dropdown_height = 76; // 76px はドロップダウンメニューの高さ\n const comment_button_rect = (event.currentTarget as HTMLElement).getBoundingClientRect();\n // メニューの表示位置をクリックされたコメントに合わせる\n this.comment_list_dropdown_top = comment_button_rect.top - comment_list_wrapper_rect.top;\n // メニューがコメントリストからはみ出るときだけ、表示位置を上側に調整\n if ((this.comment_list_dropdown_top + comment_list_dropdown_height) > comment_list_wrapper_rect.height) {\n this.comment_list_dropdown_top = this.comment_list_dropdown_top - comment_list_dropdown_height + comment_button_rect.height;\n }\n // 表示位置を調整できたので、メニューを表示\n this.comment_list_dropdown_comment = comment;\n this.is_comment_list_dropdown_display = true;\n },\n\n // ドロップダウンメニューを非表示にする\n hideCommentListDropdown() {\n this.is_comment_list_dropdown_display = false;\n this.comment_list = this.comment_list.filter((comment) => {\n return CommentUtils.isMutedComment(comment.text, comment.user_id) === false;\n });\n },\n\n // ミュートするキーワードを追加する\n addMutedKeywords() {\n CommentUtils.addMutedKeywords(this.comment_list_dropdown_comment.text);\n this.hideCommentListDropdown();\n },\n\n // ミュートするニコニコユーザー ID を追加する\n addMutedNiconicoUserIds() {\n CommentUtils.addMutedNiconicoUserIDs(this.comment_list_dropdown_comment.user_id);\n this.hideCommentListDropdown();\n },\n\n // コメントリストを一番下までスクロールする\n async scrollCommentList(smooth: boolean = false) {\n\n // ドロップダウンメニュー表示中なら手動スクロールモードに設定\n if (this.is_comment_list_dropdown_display === true) {\n this.is_manual_scroll = true;\n }\n\n // 手動スクロールモードの時は実行しない\n if (this.is_manual_scroll === true) return;\n\n // 自動スクロール中のフラグを立てる\n this.is_auto_scrolling = true;\n\n // 0.01 秒待って実行し、念押しで2回実行しないと完全に最下部までスクロールされない…(ブラウザの描画バグ?)\n // this.$nextTick() は効かなかった\n for (let index = 0; index < 3; index++) {\n await Utils.sleep(0.01);\n if (smooth === true) { // スムーズスクロール\n this.comment_list_element.scrollTo({top: this.comment_list_element.scrollHeight, left: 0, behavior: 'smooth'});\n } else {\n this.comment_list_element.scrollTo(0, this.comment_list_element.scrollHeight);\n }\n }\n\n // 0.1 秒待つ(重要)\n await Utils.sleep(0.1);\n\n // 自動スクロール中のフラグを降ろす\n this.is_auto_scrolling = false;\n },\n\n // リサイズ時のイベントを初期化\n // プレイヤーが初期化される毎に実行する必要がある\n initReserveObserver() {\n\n // 以前に初期化された ResizeObserver を終了\n if (this.resize_observer !== null) {\n this.resize_observer.unobserve(this.resize_observer_element);\n }\n\n // 監視対象の要素\n this.resize_observer_element = document.querySelector('.watch-player');\n\n // タイムアウト ID\n // 一時的に無効にした transition を有効化する際に利用する\n let animation_timeout_id = null;\n\n // プレイヤーの要素がリサイズされた際に発火するイベント\n const on_resize = () => {\n\n // 映像の要素\n const video_element = document.querySelector('.dplayer-video-wrap-aspect');\n\n // コメント描画領域の要素\n const comment_area_element = document.querySelector('.dplayer-danmaku');\n\n // プレイヤー全体と映像の高さの差(レターボックス)から、コメント描画領域の高さを狭める必要があるかを判定する\n // 2で割っているのは単体の差を測るため\n if (this.resize_observer_element === null || this.resize_observer_element.clientHeight === null) return;\n if (video_element === null || video_element.clientHeight === null) return;\n const letter_box_height = (this.resize_observer_element.clientHeight - video_element.clientHeight) / 2;\n\n const threshold = Utils.isSmartphoneVertical() ? 0 : window.matchMedia('(max-height: 450px)').matches ? 50 : 66;\n if (letter_box_height < threshold) {\n\n // コメント描画領域に必要な上下マージン\n const comment_area_vertical_margin = (threshold - letter_box_height) * 2;\n\n // 狭めるコメント描画領域の幅\n // 映像の要素の幅をそのまま利用する\n const comment_area_width = video_element.clientWidth;\n\n // 狭めるコメント描画領域の高さ\n const comment_area_height = video_element.clientHeight - comment_area_vertical_margin;\n\n // 狭めるコメント描画領域のアスペクト比を求める\n // https://tech.arc-one.jp/asepct-ratio/\n const gcd = (x: number, y: number) => { // 最大公約数を求める関数\n if (y === 0) return x;\n return gcd(y, x % y);\n };\n // 幅と高さの最大公約数を求める\n const gcd_result = gcd(comment_area_width, comment_area_height);\n // 幅と高さをそれぞれ最大公約数で割ってアスペクト比を算出\n const comment_area_height_aspect = `${comment_area_width / gcd_result} / ${comment_area_height / gcd_result}`;\n\n // 一時的に transition を無効化する\n // アスペクト比の設定は連続して行われるが、その際に transition が適用されるとワンテンポ遅れたアニメーションになってしまう\n comment_area_element.style.transition = 'none';\n\n // コメント描画領域に算出したアスペクト比を設定する\n comment_area_element.style.setProperty('--comment-area-aspect-ratio', comment_area_height_aspect);\n\n // コメント描画領域に必要な上下マージンを設定する\n comment_area_element.style.setProperty('--comment-area-vertical-margin', `${comment_area_vertical_margin}px`);\n\n // 以前セットされた setTimeout() を止める\n window.clearTimeout(animation_timeout_id);\n\n // 0.2秒後に実行する\n // 0.2秒より前にもう一度リサイズイベントが来た場合はタイマーがクリアされるため実行されない\n window.setTimeout(() => {\n\n // 再び transition を有効化する\n comment_area_element.style.transition = '';\n\n }, 0.2 * 1000);\n\n } else {\n\n // コメント描画領域に設定したアスペクト比・上下マージンを削除する\n comment_area_element.style.removeProperty('--comment-area-aspect-ratio');\n comment_area_element.style.removeProperty('--comment-area-vertical-margin');\n }\n };\n\n // 要素の監視を開始\n this.resize_observer = new ResizeObserver(on_resize);\n this.resize_observer.observe(this.resize_observer_element);\n\n // 0.6 秒待ってから初回実行\n // チャンネル切り替え後、再初期化されたプレイヤーに適用するため(早いと再初期化前のプレイヤーに適用されてしまう)\n window.setTimeout(on_resize, 0.6 * 1000);\n },\n\n // ニコニコ実況に接続し、セッションを初期化する\n async initSession(player: DPlayer, channel_id: string) {\n\n // リサイズ時のイベントを初期化\n // イベントはプレイヤーの DOM に紐づいているため、プレイヤーが破棄→再初期化される毎に実行する必要がある\n this.initReserveObserver();\n\n // タブが非表示状態のときにコメントを格納する配列\n // タブが表示状態になったらコメントリストにのみ表示する(遅れているのでプレイヤーには表示しない)\n const comment_list_buffer: ICommentData[] = [];\n\n // コメントの最大保持数\n const max_comment_count = 500;\n\n // LiveCommentManager を初期化\n this.live_comment_manager = new LiveCommentManager(\n // DPlayer のインスタンス\n player,\n // チャンネル ID\n channel_id,\n\n // 初回の過去コメント (最大50件) を受信したときのコールバック\n async (initial_comments) => {\n\n // コメントリストに一括で追加\n this.comment_list.push(...initial_comments);\n\n // コメントリストを一番下までスクロール\n this.scrollCommentList();\n },\n\n // コメントを受信したときのコールバック\n // プレイヤーへの描画は LiveCommentManager が行う\n async (comment) => {\n\n // タブが非表示状態のときは、バッファにコメントを追加するだけで終了する\n // ここで追加すると、タブが表示状態になったときに一斉に描画されて大変なことになる\n if (document.visibilityState === 'hidden') {\n comment_list_buffer.push(comment);\n return;\n }\n\n // コメントリストのコメント数が max_comment_count 件を超えたら、古いものから順に削除する\n // 仮想スクロールとはいえ、さすがに max_comment_count 件を超えると重くなりそう\n // 手動スクロール時は実行しない\n if (this.comment_list.length >= max_comment_count && this.is_manual_scroll === false) {\n this.comment_list.splice(0, Math.max(0, this.comment_list.length - max_comment_count));\n }\n\n // コメントリストに追加\n this.comment_list.push(comment);\n\n // コメントリストを一番下までスクロール\n this.scrollCommentList();\n },\n );\n\n // タブが表示状態になったときのイベント\n this.visibilitychange_listener = () => {\n if (document.visibilityState === 'visible') {\n\n // コメントリスト + バッファの合計コメント数が max_comment_count 件を超えたら、\n // コメントリスト内のコメントを古いものから順に削除し、max_comment_count 件になるようにする\n const comment_list_and_buffer_length = this.comment_list.length + comment_list_buffer.length;\n if (comment_list_and_buffer_length >= max_comment_count && this.is_manual_scroll === false) {\n this.comment_list.splice(0, Math.max(0, comment_list_and_buffer_length - max_comment_count));\n }\n\n // バッファ内のコメントをコメントリストに一括で追加する\n this.comment_list.push(...comment_list_buffer);\n comment_list_buffer.length = 0; // バッファを空にする\n\n // コメントリストを一番下までスクロール\n this.scrollCommentList();\n }\n };\n document.addEventListener('visibilitychange', this.visibilitychange_listener);\n\n // ニコニコ実況セッションを初期化する\n const result = await this.live_comment_manager.initSession();\n\n // ニコニコ実況セッションの初期化に失敗した\n // 初期化に失敗した際のエラーメッセージを保存しておく (エラー表示などで利用する)\n // プレイヤーへのエラー表示はすでに LiveCommentManager の方で行われているので、ここでは何もしない\n if (result.is_success === false) {\n this.initialize_failed_message = result.detail;\n }\n },\n\n // コメントを送信する\n sendComment(options: DPlayerType.APIBackendSendOptions) {\n\n // 初期化に失敗しているときは実行せず、保存しておいたエラーメッセージを表示する\n if (this.initialize_failed_message !== null) {\n options.error(this.initialize_failed_message);\n return;\n }\n\n // バリデーション\n if (this.userStore.user === null) {\n options.error('コメントするには、KonomiTV アカウントにログインしてください。');\n return;\n }\n if (this.userStore.user.niconico_user_id === null) {\n options.error('コメントするには、ニコニコアカウントと連携してください。');\n return;\n }\n if (this.userStore.user.niconico_user_premium === false && (options.data.type === 'top' || options.data.type === 'bottom')) {\n options.error('コメントを上下に固定するには、ニコニコアカウントのプレミアム会員登録が必要です。');\n return;\n }\n if (this.userStore.user.niconico_user_premium === false && options.data.size === 'big') {\n options.error('コメントサイズを大きめに設定するには、ニコニコアカウントのプレミアム会員登録が必要です。');\n return;\n }\n\n // ニコニコ実況のコメントサーバーにコメントを送信\n this.live_comment_manager.sendComment(options);\n },\n\n // ニコニコ実況セッションを破棄する\n destroy() {\n\n // タブの表示/非表示の状態が切り替わったときのイベントを削除\n if (this.visibilitychange_listener !== null) {\n document.removeEventListener('visibilitychange', this.visibilitychange_listener);\n this.visibilitychange_listener = null;\n }\n\n // LiveCommentManager を破棄\n if (this.live_comment_manager !== null) {\n this.live_comment_manager.destroy();\n this.live_comment_manager = null;\n }\n\n this.initialize_failed_message = null;\n this.comment_list = [];\n }\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Comment.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Comment.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Comment.vue?vue&type=template&id=6f8c784f&scoped=true&\"\nimport script from \"./Comment.vue?vue&type=script&lang=ts&\"\nexport * from \"./Comment.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Comment.vue?vue&type=style&index=0&id=6f8c784f&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"6f8c784f\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"program-container\"},[_c('section',{staticClass:\"program-broadcaster\"},[_c('img',{staticClass:\"program-broadcaster__icon\",attrs:{\"src\":`${_vm.Utils.api_base_url}/channels/${(_vm.channelsStore.channel_id)}/logo`}}),_c('div',{staticClass:\"program-broadcaster__number\"},[_vm._v(\"Ch: \"+_vm._s(_vm.channelsStore.channel.current.channel_number))]),_c('div',{staticClass:\"program-broadcaster__name\"},[_vm._v(_vm._s(_vm.channelsStore.channel.current.channel_name))])]),_c('section',{staticClass:\"program-info\"},[_c('h1',{staticClass:\"program-info__title\",domProps:{\"innerHTML\":_vm._s(_vm.ProgramUtils.decorateProgramInfo(_vm.channelsStore.channel.current.program_present, 'title'))}}),_c('div',{staticClass:\"program-info__time\"},[_vm._v(\" \"+_vm._s(_vm.ProgramUtils.getProgramTime(_vm.channelsStore.channel.current.program_present))+\" \")]),_c('div',{staticClass:\"program-info__description\",domProps:{\"innerHTML\":_vm._s(_vm.ProgramUtils.decorateProgramInfo(_vm.channelsStore.channel.current.program_present, 'description'))}}),_c('div',{staticClass:\"program-info__genre-container\"},_vm._l((_vm.ProgramUtils.getAttribute(_vm.channelsStore.channel.current.program_present, 'genre', [])),function(genre,genre_index){return _c('div',{key:genre_index,staticClass:\"program-info__genre\"},[_vm._v(\" \"+_vm._s(genre.major)+\" / \"+_vm._s(genre.middle)+\" \")])}),0),_c('div',{staticClass:\"program-info__next\"},[_c('span',{staticClass:\"program-info__next-decorate\"},[_vm._v(\"NEXT\")]),_c('Icon',{staticClass:\"program-info__next-icon\",attrs:{\"icon\":\"fluent:fast-forward-20-filled\",\"width\":\"16px\"}})],1),_c('span',{staticClass:\"program-info__next-title\",domProps:{\"innerHTML\":_vm._s(_vm.ProgramUtils.decorateProgramInfo(_vm.channelsStore.channel.current.program_following, 'title'))}}),_c('div',{staticClass:\"program-info__next-time\"},[_vm._v(\" \"+_vm._s(_vm.ProgramUtils.getProgramTime(_vm.channelsStore.channel.current.program_following))+\" \")]),_c('div',{staticClass:\"program-info__status\"},[_c('div',{staticClass:\"program-info__status-force\",class:`program-info__status-force--${_vm.ChannelUtils.getChannelForceType(_vm.channelsStore.channel.current.channel_force)}`},[_c('Icon',{attrs:{\"icon\":\"fa-solid:fire-alt\",\"height\":\"14px\"}}),_c('span',{staticClass:\"ml-2\"},[_vm._v(\"勢い:\")]),_c('span',{staticClass:\"ml-2\"},[_vm._v(_vm._s(_vm.ProgramUtils.getAttribute(_vm.channelsStore.channel.current, 'channel_force', '--'))+\" コメ/分\")])],1),_c('div',{staticClass:\"program-info__status-viewers ml-5\"},[_c('Icon',{attrs:{\"icon\":\"fa-solid:eye\",\"height\":\"14px\"}}),_c('span',{staticClass:\"ml-2\"},[_vm._v(\"視聴数:\")]),_c('span',{staticClass:\"ml-1\"},[_vm._v(_vm._s(_vm.channelsStore.channel.current.viewers))])],1)])]),_c('section',{staticClass:\"program-detail-container\"},_vm._l((_vm.ProgramUtils.getAttribute(_vm.channelsStore.channel.current.program_present, 'detail', {})),function(detail_text,detail_heading){return _c('div',{key:detail_heading,staticClass:\"program-detail\"},[_c('h2',{staticClass:\"program-detail__heading\"},[_vm._v(_vm._s(detail_heading))]),_c('div',{staticClass:\"program-detail__text\",domProps:{\"innerHTML\":_vm._s(_vm.Utils.URLtoLink(detail_text))}})])}),0)])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport useChannelsStore from '@/store/ChannelsStore';\nimport Utils, { ChannelUtils, ProgramUtils } from '@/utils';\n\nexport default Vue.extend({\n name: 'Panel-ProgramTab',\n data() {\n return {\n // ユーティリティをテンプレートで使えるように\n Utils: Utils,\n ChannelUtils: ChannelUtils,\n ProgramUtils: ProgramUtils,\n };\n },\n computed: {\n // ChannelsStore に this.channelsStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useChannelsStore),\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Program.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Program.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Program.vue?vue&type=template&id=12609bdf&scoped=true&\"\nimport script from \"./Program.vue?vue&type=script&lang=ts&\"\nexport * from \"./Program.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Program.vue?vue&type=style&index=0&id=12609bdf&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"12609bdf\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"twitter-container\"},[_c('v-dialog',{attrs:{\"content-class\":\"zoom-capture-modal-container\",\"max-width\":\"980\",\"transition\":\"slide-y-transition\"},model:{value:(_vm.zoom_capture_modal),callback:function ($$v) {_vm.zoom_capture_modal=$$v},expression:\"zoom_capture_modal\"}},[_c('div',{staticClass:\"zoom-capture-modal\"},[_c('img',{staticClass:\"zoom-capture-modal__image\",attrs:{\"src\":_vm.zoom_capture ? _vm.zoom_capture.image_url: ''}}),_c('a',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"zoom-capture-modal__download\",attrs:{\"href\":_vm.zoom_capture ? _vm.zoom_capture.image_url : '',\"download\":_vm.zoom_capture ? _vm.zoom_capture.filename : ''}},[_c('Icon',{attrs:{\"icon\":\"fa6-solid:download\",\"width\":\"45px\"}})],1)])]),_c('div',{staticClass:\"tab-container\"},[_c('div',{staticClass:\"tab-content tab-content--search\",class:{'tab-content--active': _vm.twitter_active_tab === 'Search'}},[_c('div',{staticClass:\"search px-4\"},[_vm._v(\" リアルタイム検索機能は鋭意開発中です。 \")])]),_c('div',{staticClass:\"tab-content tab-content--timeline\",class:{'tab-content--active': _vm.twitter_active_tab === 'Timeline'}},[_c('div',{staticClass:\"search px-4\"},[_vm._v(\" タイムライン機能は鋭意開発中です。 \")])]),_c('div',{staticClass:\"tab-content tab-content--capture\",class:{'tab-content--active': _vm.twitter_active_tab === 'Capture'}},[_c('div',{staticClass:\"captures\"},_vm._l((_vm.captures),function(capture){return _c('div',{key:capture.image_url,staticClass:\"capture\",class:{\n 'capture--selected': capture.selected,\n 'capture--focused': capture.focused,\n 'capture--disabled': !capture.selected && _vm.tweet_captures.length >= 4,\n },on:{\"click\":function($event){return _vm.clickCapture(capture)}}},[_c('img',{staticClass:\"capture__image\",attrs:{\"src\":capture.image_url}}),_c('div',{staticClass:\"capture__disabled-cover\"}),_c('div',{staticClass:\"capture__selected-number\"},[_vm._v(_vm._s(_vm.tweet_captures.findIndex(blob => blob === capture.blob) + 1))]),_c('Icon',{staticClass:\"capture__selected-checkmark\",attrs:{\"icon\":\"fluent:checkmark-circle-16-filled\"}}),_c('div',{staticClass:\"capture__selected-border\"}),_c('div',{staticClass:\"capture__focused-border\"}),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"capture__zoom\",on:{\"click\":function($event){$event.preventDefault();$event.stopPropagation();_vm.zoom_capture_modal = true; _vm.zoom_capture = capture},\"mousedown\":function($event){$event.preventDefault();$event.stopPropagation();/* 親要素の波紋が広がらないように */}}},[_c('Icon',{attrs:{\"icon\":\"fluent:zoom-in-16-regular\",\"width\":\"32px\"}})],1)],1)}),0),_c('div',{directives:[{name:\"show\",rawName:\"v-show\",value:(_vm.captures.length === 0),expression:\"captures.length === 0\"}],staticClass:\"capture-announce\"},[_c('div',{staticClass:\"capture-announce__heading\"},[_vm._v(\"まだキャプチャがありません。\")]),_vm._m(0)])])]),_c('div',{staticClass:\"tab-button-container\"},[_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"tab-button\",class:{'tab-button--active': _vm.twitter_active_tab === 'Search'},on:{\"click\":function($event){_vm.twitter_active_tab = 'Search'}}},[_c('Icon',{attrs:{\"icon\":\"fluent:search-16-filled\",\"height\":\"18px\"}}),_c('span',{staticClass:\"tab-button__text\"},[_vm._v(\"ツイート検索\")])],1),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"tab-button\",class:{'tab-button--active': _vm.twitter_active_tab === 'Timeline'},on:{\"click\":function($event){_vm.twitter_active_tab = 'Timeline'}}},[_c('Icon',{attrs:{\"icon\":\"fluent:home-16-regular\",\"height\":\"18px\"}}),_c('span',{staticClass:\"tab-button__text\"},[_vm._v(\"タイムライン\")])],1),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"tab-button\",class:{'tab-button--active': _vm.twitter_active_tab === 'Capture'},on:{\"click\":function($event){_vm.twitter_active_tab = 'Capture'}}},[_c('Icon',{attrs:{\"icon\":\"fluent:image-copy-20-regular\",\"height\":\"18px\"}}),_c('span',{staticClass:\"tab-button__text\"},[_vm._v(\"キャプチャ\")])],1)]),_c('div',{staticClass:\"tweet-form\",class:{\n 'tweet-form--focused': _vm.is_tweet_hashtag_form_focused || _vm.is_tweet_text_form_focused,\n 'tweet-form--virtual-keyboard-display': _vm.is_virtual_keyboard_display &&\n (_vm.Utils.hasActiveElementClass('tweet-form__hashtag-form') || _vm.Utils.hasActiveElementClass('tweet-form__textarea')) &&\n (() => {_vm.is_hashtag_list_display = false; return true;})(),\n }},[_c('div',{staticClass:\"tweet-form__hashtag\"},[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.tweet_hashtag),expression:\"tweet_hashtag\"}],staticClass:\"tweet-form__hashtag-form\",attrs:{\"type\":\"search\",\"placeholder\":\"#ハッシュタグ\",\"spellcheck\":\"false\"},domProps:{\"value\":(_vm.tweet_hashtag)},on:{\"input\":[function($event){if($event.target.composing)return;_vm.tweet_hashtag=$event.target.value},function($event){return _vm.updateTweetLetterCount()}],\"focus\":function($event){_vm.is_tweet_hashtag_form_focused = true},\"blur\":function($event){_vm.is_tweet_hashtag_form_focused = false},\"change\":function($event){_vm.tweet_hashtag = _vm.formatHashtag(_vm.tweet_hashtag); _vm.updateTweetLetterCount()}}}),_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"tweet-form__hashtag-list-button\",on:{\"click\":function($event){return _vm.clickHashtagListButton()}}},[_c('Icon',{attrs:{\"icon\":\"fluent:clipboard-text-ltr-32-regular\",\"height\":\"22px\"}})],1)]),_c('textarea',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.tweet_text),expression:\"tweet_text\"}],ref:\"tweet_text\",staticClass:\"tweet-form__textarea\",attrs:{\"placeholder\":\"ツイート\",\"spellcheck\":\"false\"},domProps:{\"value\":(_vm.tweet_text)},on:{\"input\":[function($event){if($event.target.composing)return;_vm.tweet_text=$event.target.value},function($event){return _vm.updateTweetLetterCount()}],\"paste\":function($event){return _vm.pasteClipboardData($event)},\"focus\":function($event){_vm.is_tweet_text_form_focused = true},\"blur\":function($event){_vm.is_tweet_text_form_focused = false}}}),_c('div',{staticClass:\"tweet-form__control\"},[_c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"account-button\",class:{'account-button--no-login': !_vm.is_logged_in_twitter},on:{\"click\":function($event){return _vm.clickAccountButton()}}},[_c('img',{staticClass:\"account-button__icon\",attrs:{\"src\":_vm.is_logged_in_twitter ? _vm.selected_twitter_account.icon_url : '/assets/images/account-icon-default.png'}}),_c('span',{staticClass:\"account-button__screen-name\"},[_vm._v(\" \"+_vm._s(_vm.is_logged_in_twitter ? `@${_vm.selected_twitter_account.screen_name}` : '連携されていません')+\" \")]),_c('Icon',{staticClass:\"account-button__menu\",attrs:{\"icon\":\"fluent:more-circle-20-regular\",\"width\":\"22px\"}})],1),_c('div',{staticClass:\"limit-meter\"},[_c('div',{staticClass:\"limit-meter__content\",class:{\n 'limit-meter__content--yellow': _vm.tweet_letter_count <= 20,\n 'limit-meter__content--red': _vm.tweet_letter_count <= 0,\n }},[_c('Icon',{staticStyle:{\"margin-right\":\"-2px\"},attrs:{\"icon\":\"fa-brands:twitter\",\"width\":\"12px\"}}),_c('span',[_vm._v(_vm._s(_vm.tweet_letter_count))])],1),_c('div',{staticClass:\"limit-meter__content\"},[_c('Icon',{attrs:{\"icon\":\"fluent:image-16-filled\",\"width\":\"14px\"}}),_c('span',[_vm._v(_vm._s(_vm.tweet_captures.length)+\"/4\")])],1)]),_c('button',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"tweet-button\",attrs:{\"disabled\":!_vm.is_logged_in_twitter || _vm.tweet_letter_count < 0 ||\n (_vm.tweet_letter_count === 140 && _vm.tweet_captures.length === 0)},on:{\"click\":function($event){return _vm.sendTweet()},\"touchstart\":function($event){return _vm.sendTweet()}}},[_c('Icon',{attrs:{\"icon\":\"fa-brands:twitter\",\"height\":\"16px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(\"ツイート\")])],1)])]),_c('div',{staticClass:\"hashtag-list\",class:{\n 'hashtag-list--display': _vm.is_hashtag_list_display,\n 'hashtag-list--virtual-keyboard-display': _vm.is_virtual_keyboard_display && _vm.Utils.hasActiveElementClass('hashtag__input'),\n }},[_c('div',{staticClass:\"hashtag-heading\"},[_c('div',{staticClass:\"hashtag-heading__text\"},[_c('Icon',{attrs:{\"icon\":\"charm:hash\",\"width\":\"17px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(\"ハッシュタグリスト\")])],1),_c('button',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"hashtag-heading__add-button\",on:{\"click\":function($event){_vm.saved_twitter_hashtags.push({id: _vm.Utils.time(), text: '#ここにハッシュタグを入力', editing: false})}}},[_c('Icon',{attrs:{\"icon\":\"fluent:add-12-filled\",\"width\":\"17px\"}}),_c('span',{staticClass:\"ml-1\"},[_vm._v(\"追加\")])],1)]),_c('draggable',{staticClass:\"hashtag-container\",attrs:{\"handle\":\".hashtag__sort-handle\"},model:{value:(_vm.saved_twitter_hashtags),callback:function ($$v) {_vm.saved_twitter_hashtags=$$v},expression:\"saved_twitter_hashtags\"}},_vm._l((_vm.saved_twitter_hashtags),function(hashtag){return _c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\",value:(!hashtag.editing),expression:\"!hashtag.editing\"}],key:hashtag.id,staticClass:\"hashtag\",class:{'hashtag--editing': hashtag.editing},on:{\"click\":function($event){return _vm.clickHashtag(hashtag)}}},[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(hashtag.text),expression:\"hashtag.text\"}],staticClass:\"hashtag__input\",attrs:{\"type\":\"search\",\"spellcheck\":\"false\",\"disabled\":!hashtag.editing},domProps:{\"value\":(hashtag.text)},on:{\"click\":function($event){$event.stopPropagation();},\"input\":function($event){if($event.target.composing)return;_vm.$set(hashtag, \"text\", $event.target.value)}}}),_c('button',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"hashtag__edit-button\",on:{\"click\":function($event){$event.preventDefault();$event.stopPropagation();hashtag.editing = !hashtag.editing;\n hashtag.text = _vm.formatHashtag(hashtag.text, true); _vm.updateTweetLetterCount()}}},[_c('Icon',{attrs:{\"icon\":hashtag.editing ? 'fluent:checkmark-16-filled': 'fluent:edit-16-filled',\"width\":\"17px\"}})],1),_c('button',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],staticClass:\"hashtag__delete-button\",on:{\"click\":function($event){$event.preventDefault();$event.stopPropagation();_vm.saved_twitter_hashtags.splice(_vm.saved_twitter_hashtags.indexOf(hashtag), 1)}}},[_c('Icon',{attrs:{\"icon\":\"fluent:delete-16-filled\",\"width\":\"17px\"}})],1),_c('div',{staticClass:\"hashtag__sort-handle\"},[_c('Icon',{attrs:{\"icon\":\"material-symbols:drag-handle-rounded\",\"width\":\"17px\"}})],1)])}),0)],1),_c('div',{staticClass:\"twitter-account-list\",class:{'twitter-account-list--display': _vm.is_twitter_account_list_display}},_vm._l((_vm.userStore.user ? _vm.userStore.user.twitter_accounts : []),function(twitter_account){return _c('div',{directives:[{name:\"ripple\",rawName:\"v-ripple\"}],key:twitter_account.id,staticClass:\"twitter-account\",on:{\"click\":function($event){return _vm.updateSelectedTwitterAccount(twitter_account)}}},[_c('img',{staticClass:\"twitter-account__icon\",attrs:{\"src\":twitter_account.icon_url}}),_c('div',{staticClass:\"twitter-account__info\"},[_c('div',{staticClass:\"twitter-account__name\"},[_vm._v(_vm._s(twitter_account.name))]),_c('div',{staticClass:\"twitter-account__screen-name\"},[_vm._v(\"@\"+_vm._s(twitter_account.screen_name))])]),_c('Icon',{directives:[{name:\"show\",rawName:\"v-show\",value:(twitter_account.id === _vm.settingsStore.settings.selected_twitter_account_id),expression:\"twitter_account.id === settingsStore.settings.selected_twitter_account_id\"}],staticClass:\"twitter-account__check\",attrs:{\"icon\":\"fluent:checkmark-16-filled\",\"width\":\"24px\"}})],1)}),0)],1)\n}\nvar staticRenderFns = [function (){var _vm=this,_c=_vm._self._c,_setup=_vm._self._setupProxy;return _c('div',{staticClass:\"capture-announce__text\"},[_c('p',{staticClass:\"mt-0 mb-0\"},[_vm._v(\"プレイヤーのキャプチャボタンやショートカットキーでキャプチャを撮ると、ここに表示されます。\")]),_c('p',{staticClass:\"mt-2 mb-0\"},[_vm._v(\"表示されたキャプチャを選択してからツイートすると、キャプチャを付けてツイートできます。\")])])\n}]\n\nexport { render, staticRenderFns }","\n\nimport DPlayer from 'dplayer';\nimport { mapStores } from 'pinia';\nimport Vue, { PropType } from 'vue';\nimport draggable from 'vuedraggable';\n\nimport Twitter from '@/services/Twitter';\nimport { ITwitterAccount } from '@/services/Users';\nimport useChannelsStore from '@/store/ChannelsStore';\nimport useSettingsStore from '@/store/SettingsStore';\nimport useUserStore from '@/store/UserStore';\nimport Utils from '@/utils';\n\n// このコンポーネント内でのキャプチャのインターフェイス\ninterface ITweetCapture {\n blob: Blob;\n filename: string;\n image_url: string;\n selected: boolean;\n focused: boolean;\n}\n\n// このコンポーネント内でのハッシュタグのインターフェイス\ninterface IHashtag {\n id: number;\n text: string;\n editing: boolean;\n}\n\nexport default Vue.extend({\n name: 'Panel-TwitterTab',\n components: {\n draggable,\n },\n props: {\n // プレイヤーのインスタンス\n player: {\n type: null as PropType, // 代入当初は null になるため苦肉の策\n required: true,\n },\n // 仮想キーボードが表示されているかどうか\n is_virtual_keyboard_display: {\n type: Boolean as PropType,\n required: true,\n },\n },\n data() {\n return {\n\n // ユーティリティをテンプレートで使えるように\n Utils: Utils,\n\n // Twitter アカウントを1つでも連携しているかどうか\n is_logged_in_twitter: false,\n\n // 現在ツイート対象として選択されている Twitter アカウント\n selected_twitter_account: null as ITwitterAccount | null,\n\n // 連携している Twitter アカウントリストを表示しているか\n is_twitter_account_list_display: false,\n\n // 保存している Twitter のハッシュタグが入るリスト\n saved_twitter_hashtags: useSettingsStore().settings.saved_twitter_hashtags.map((hashtag, index) => {\n // id プロパティは :key=\"\" に指定するためにつける ID (ミリ秒単位のタイムスタンプ + index で適当に一意になるように)\n return {id: Utils.time() + index, text: hashtag, editing: false} as IHashtag;\n }),\n\n // ハッシュタグリストを表示しているか\n is_hashtag_list_display: false,\n\n // デフォルトで表示される Twitter タブ内のタブ\n twitter_active_tab: useSettingsStore().settings.twitter_active_tab,\n\n // キャプチャを拡大表示するモーダルの表示状態\n zoom_capture_modal: false,\n\n // 現在モーダルで拡大表示中のキャプチャのオブジェクト\n zoom_capture: null as ITweetCapture | null,\n\n // キャプチャリスト\n captures: [] as ITweetCapture[],\n\n // キャプチャリストの要素\n captures_element: null as HTMLDivElement | null,\n\n // ツイートハッシュタグフォームにフォーカスしているか\n is_tweet_hashtag_form_focused: false,\n\n // ツイート本文フォームにフォーカスしているか\n is_tweet_text_form_focused: false,\n\n // ツイートのハッシュタグ\n tweet_hashtag: '',\n\n // ツイート本文\n tweet_text: '',\n\n // ツイートに添付するキャプチャの Blob のリスト\n tweet_captures: [] as Blob[],\n\n // 文字数カウント\n tweet_letter_count: 140,\n\n // ツイートを送信中か (API リクエストを実行するまで)\n is_tweet_sending: false,\n };\n },\n computed: {\n // ChannelsStore / SettingsStore / UserStore に this.channelsStore / this.settingsStore / this.userStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useChannelsStore, useSettingsStore, useUserStore),\n },\n async created() {\n\n // アカウント情報を更新\n await this.userStore.fetchUser();\n\n // ログイン時のみ\n if (this.userStore.is_logged_in === true) {\n\n // 連携している Twitter アカウントがあれば true に設定\n if (this.userStore.user.twitter_accounts.length > 0) {\n this.is_logged_in_twitter = true;\n\n // 現在ツイート対象として選択されている Twitter アカウントの ID が設定されていない or ID に紐づく Twitter アカウントがない\n // 連携している Twitter アカウントのうち、一番最初のものを自動選択する\n // ここで言う Twitter アカウントの ID は DB 上で連番で振られるもので、Twitter アカウントそのものの固有 ID ではない\n if (this.settingsStore.settings.selected_twitter_account_id === null ||\n !this.userStore.user.twitter_accounts.some((twitter_account) => {\n return twitter_account.id === this.settingsStore.settings.selected_twitter_account_id;\n })) {\n this.settingsStore.settings.selected_twitter_account_id = this.userStore.user.twitter_accounts[0].id;\n }\n\n // 現在ツイート対象として選択されている Twitter アカウントを取得・設定\n const twitter_account_index = this.userStore.user.twitter_accounts.findIndex((twitter_account) => {\n // Twitter アカウントの ID が選択されているものと一致する\n return twitter_account.id === this.settingsStore.settings.selected_twitter_account_id;\n });\n this.selected_twitter_account = this.userStore.user.twitter_accounts[twitter_account_index];\n }\n }\n\n // 局タグ追加処理を走らせる (ハッシュタグフォームのフォーマット処理も同時に行われるが、元々空なので無意味)\n this.tweet_hashtag = this.formatHashtag(this.tweet_hashtag);\n this.updateTweetLetterCount();\n },\n beforeDestroy() {\n // 終了前にすべてのキャプチャの Blob URL を revoke してリソースを解放する\n for (const capture of this.captures) {\n URL.revokeObjectURL(capture.image_url);\n }\n },\n watch: {\n\n // 保存しているハッシュタグが変更されたら随時 LocalStorage に保存する\n saved_twitter_hashtags: {\n deep: true,\n handler() {\n this.settingsStore.settings.saved_twitter_hashtags = this.saved_twitter_hashtags.map(hashtag => hashtag.text);\n }\n }\n },\n methods: {\n\n // 文字数カウントを変更するイベント\n updateTweetLetterCount() {\n\n // サロゲートペアを考慮し、スプレッド演算子で一度配列化してから数えている\n // ref: https://qiita.com/suin/items/3da4fb016728c024eaca\n this.tweet_letter_count = 140 - [...this.tweet_hashtag].length - [...this.tweet_text].length;\n },\n\n // クリップボード内のデータがペーストされたときのイベント\n pasteClipboardData(event: ClipboardEvent) {\n\n // 一応配列になっているので回しているが、基本1回のペーストにつき DataTransferItem は1個しか入らない\n for (const clipboard_item of event.clipboardData.items) {\n\n // 画像のみを対象にする (DataTransferItem.type には MIME タイプが入る)\n if (clipboard_item.type.startsWith('image/')) {\n\n // クリップボード内の画像データを File オブジェクトとして取得し、キャプチャリストに追加\n const file = clipboard_item.getAsFile();\n this.addCaptureList(file, file.name);\n }\n }\n },\n\n // ハッシュタグリストボタンが押されたときのイベント\n clickHashtagListButton() {\n this.is_hashtag_list_display = !this.is_hashtag_list_display;\n // すべてのハッシュタグの編集状態を解除する\n for (const hashtag of this.saved_twitter_hashtags) {\n hashtag.editing = false;\n }\n },\n\n // ハッシュタグがクリックされたときのイベント\n clickHashtag(hashtag: IHashtag) {\n this.tweet_hashtag = hashtag.text;\n this.tweet_hashtag = this.formatHashtag(this.tweet_hashtag);\n this.updateTweetLetterCount();\n window.setTimeout(() => this.is_hashtag_list_display = false, 150);\n },\n\n // アカウントボタンが押されたときのイベント\n clickAccountButton() {\n\n // Twitter アカウントが連携されていない場合は Twitter 設定画面に飛ばす\n if (!this.is_logged_in_twitter) {\n\n // 視聴画面以外に遷移するため、フルスクリーンを解除しないと画面が崩れる\n if (document.fullscreenElement) {\n document.exitFullscreen();\n }\n\n this.$router.push({path: '/settings/twitter'});\n return;\n }\n\n // アカウントリストの表示/非表示を切り替え\n this.is_twitter_account_list_display = !this.is_twitter_account_list_display;\n\n // アカウントリストが表示されているなら、ハッシュタグリストを非表示にする\n if (this.is_twitter_account_list_display === true) {\n this.is_hashtag_list_display = false;\n }\n },\n\n // 選択されている Twitter アカウントを更新する\n updateSelectedTwitterAccount(twitter_account: ITwitterAccount) {\n this.settingsStore.settings.selected_twitter_account_id = twitter_account.id;\n this.selected_twitter_account = twitter_account;\n\n // Twitter アカウントリストのオーバーレイを閉じる (少し待ってから閉じたほうが体感が良い)\n window.setTimeout(() => this.is_twitter_account_list_display = false, 150);\n },\n\n // キャプチャリスト内のキャプチャがクリックされたときのイベント\n clickCapture(capture: ITweetCapture) {\n\n // 選択されたキャプチャが3枚まで & まだ選択されていないならキャプチャをツイート対象に追加する\n if (this.tweet_captures.length < 4 && capture.selected === false) {\n capture.selected = true;\n this.tweet_captures.push(capture.blob);\n } else {\n // ツイート対象のキャプチャになっていたら取り除く\n const index = this.tweet_captures.findIndex(blob => blob === capture.blob);\n if (index > -1) {\n this.tweet_captures.splice(index, 1);\n }\n // キャプチャの選択を解除\n capture.selected = false;\n }\n },\n\n // 撮ったキャプチャを親コンポーネントから受け取り、キャプチャリストに追加する\n async addCaptureList(blob: Blob, filename: string) {\n\n if (this.captures_element === null) {\n this.captures_element = this.$el.querySelector('.tab-content--capture');\n }\n\n // 撮ったキャプチャが50件を超えていたら、重くなるので古いものから削除する\n // 削除する前に Blob URL を revoke してリソースを解放するのがポイント\n if (this.captures.length > 50) {\n URL.revokeObjectURL(this.captures[0].image_url);\n this.captures.shift();\n }\n\n // キャプチャリストにキャプチャを追加\n const blob_url = URL.createObjectURL(blob);\n this.captures.push({\n blob: blob,\n filename: filename,\n image_url: blob_url,\n selected: false,\n focused: false,\n });\n\n // キャプチャリストを下にスクロール\n // this.$nextTick() のコールバックで DOM の更新を待つ\n this.$nextTick(() => {\n this.captures_element.scrollTo({\n top: this.captures_element.scrollHeight,\n behavior: 'smooth',\n });\n });\n },\n\n // 撮ったキャプチャに番組タイトルの透かしを描画する\n async drawProgramTitleOnCapture(capture: Blob): Promise {\n\n // キャプチャの Blob を createImageBitmap() で Canvas に描ける ImageBitmap に変換\n const image_bitmap = await createImageBitmap(capture);\n\n // OffscreenCanvas が使えるなら使う (OffscreenCanvas の方がパフォーマンスが良い)\n const canvas = ('OffscreenCanvas' in window) ?\n new OffscreenCanvas(image_bitmap.width, image_bitmap.height) : document.createElement('canvas');\n\n // Canvas にキャプチャを描画\n const context = canvas.getContext('2d', {\n alpha: false,\n desynchronized: true,\n willReadFrequently: false,\n }) as OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;\n context.drawImage(image_bitmap, 0, 0);\n image_bitmap.close();\n\n // 描画設定\n context.font = 'bold 22px \"YakuHanJPs\", \"Open Sans\", \"Hiragino Sans\", \"Noto Sans JP\", sans-serif'; // フォント\n context.fillStyle = 'rgba(255, 255, 255, 70%)'; // 半透明の白\n context.shadowColor = 'rgba(0, 0, 0, 100%)'; // 影の色\n context.shadowBlur = 4; // 影をぼかすしきい値\n context.shadowOffsetX = 0; // 影のX座標\n context.shadowOffsetY = 0; // 影のY座標\n\n // 番組タイトルの透かしを描画\n const title = this.channelsStore.channel.current.program_present?.title ?? '放送休止';\n switch (this.settingsStore.settings.tweet_capture_watermark_position) {\n case 'TopLeft': {\n context.textAlign = 'left'; // 左寄せ\n context.textBaseline = 'top'; // ベースラインを上寄せ\n context.fillText(title, 16, 12);\n break;\n }\n case 'TopRight': {\n context.textAlign = 'right'; // 右寄せ\n context.textBaseline = 'top'; // ベースラインを上寄せ\n context.fillText(title, canvas.width - 16, 12);\n break;\n }\n case 'BottomLeft': {\n context.textAlign = 'left'; // 左寄せ\n context.textBaseline = 'bottom'; // ベースラインを下寄せ\n context.fillText(title, 16, canvas.height - 12);\n break;\n }\n case 'BottomRight': {\n context.textAlign = 'right'; // 右寄せ\n context.textBaseline = 'bottom'; // ベースラインを下寄せ\n context.fillText(title, canvas.width - 16, canvas.height - 12);\n break;\n }\n }\n\n // Blob にして返す\n if (canvas instanceof OffscreenCanvas) {\n return await canvas.convertToBlob({type: 'image/jpeg', quality: 1});\n } else {\n return new Promise(resolve => canvas.toBlob(blob => resolve(blob), 'image/jpeg', 1));\n }\n },\n\n // チャンネル名から対応する局タグを取得する\n // とりあえず三大首都圏 + BS のみ対応\n getChannelHashtag(channel_name: string): string | null {\n // NHK\n if (channel_name.startsWith('NHK総合')) {\n return '#nhk';\n } else if (channel_name.startsWith('NHKEテレ')) {\n return '#etv';\n // 民放\n } else if (channel_name.startsWith('日テレ')) {\n return '#ntv';\n } else if (channel_name.startsWith('読売テレビ')) {\n return '#ytv';\n } else if (channel_name.startsWith('中京テレビ')) {\n return '#chukyotv';\n } else if (channel_name.startsWith('テレビ朝日')) {\n return '#tvasahi';\n } else if (channel_name.startsWith('ABCテレビ')) {\n return '#abc';\n } else if (channel_name.startsWith('メ~テレ')) {\n return '#nagoyatv';\n } else if (channel_name.startsWith('TBS') && !channel_name.includes('TBSチャンネル')) {\n return '#tbs';\n } else if (channel_name.startsWith('MBS')) {\n return '#mbs';\n } else if (channel_name.startsWith('CBC')) {\n return '#cbc';\n } else if (channel_name.startsWith('テレビ東京')) {\n return '#tvtokyo';\n } else if (channel_name.startsWith('テレビ大阪')) {\n return '#tvo';\n } else if (channel_name.startsWith('テレビ愛知')) {\n return '#tva';\n } else if (channel_name.startsWith('フジテレビ')) {\n return '#fujitv';\n } else if (channel_name.startsWith('関西テレビ')) {\n return '#kantele';\n } else if (channel_name.startsWith('東海テレビ')) {\n return '#tokaitv';\n // 独立局\n } else if (channel_name.startsWith('TOKYO MX')) {\n return '#tokyomx';\n } else if (channel_name.startsWith('tvk')) {\n return '#tvk';\n } else if (channel_name.startsWith('チバテレ')) {\n return '#chibatv';\n } else if (channel_name.startsWith('テレ玉')) {\n return '#teletama';\n } else if (channel_name.startsWith('サンテレビ')) {\n return '#suntv';\n } else if (channel_name.startsWith('KBS京都')) {\n return '#kbs';\n // BS・CS\n } else if (channel_name.startsWith('NHKBS1')) {\n return '#nhkbs1';\n } else if (channel_name.startsWith('NHKBSプレミアム')) {\n return '#nhkbsp';\n } else if (channel_name.startsWith('BS日テレ')) {\n return '#bsntv';\n } else if (channel_name.startsWith('BS朝日')) {\n return '#bsasahi';\n } else if (channel_name.startsWith('BS-TBS')) {\n return '#bstbs';\n } else if (channel_name.startsWith('BSテレ東')) {\n return '#bstvtokyo';\n } else if (channel_name.startsWith('BSフジ')) {\n return '#bsfuji';\n } else if (channel_name.startsWith('BS11イレブン')) {\n return '#bs11';\n } else if (channel_name.startsWith('BS12トゥエルビ')) {\n return '#bs12';\n } else if (channel_name.startsWith('AT-X')) {\n return '#at_x';\n }\n\n return null;\n },\n\n // ハッシュタグを整形(余計なスペースなどを削り、全角ハッシュを半角ハッシュへ、全角スペースを半角スペースに置換)\n formatHashtag(tweet_hashtag: string, from_hashtag_list: boolean = false): string {\n\n // ハッシュとスペースの表記ゆれを統一し、連続するハッシュやスペースを1つにする\n const tweet_hashtag_array = tweet_hashtag.trim()\n .replaceAll('♯', '#').replaceAll('#', '#').replace(/#{2,}/g, '#').replaceAll(' ', ' ').replaceAll(/ +/g,' ').split(' ')\n .filter(hashtag => hashtag !== '');\n\n // ハッシュタグがついてない場合にハッシュタグを付与\n for (let index in tweet_hashtag_array) {\n if (!tweet_hashtag_array[index].startsWith('#')) {\n tweet_hashtag_array[index] = `#${tweet_hashtag_array[index]}`;\n }\n }\n\n // 設定でオンになっている場合のみ、視聴中チャンネルの局タグを自動で追加する (ハッシュタグリスト内のハッシュタグは除外)\n if (this.settingsStore.settings.auto_add_watching_channel_hashtag === true && from_hashtag_list === false) {\n const channel_hashtag = this.getChannelHashtag(this.channelsStore.channel.current.channel_name);\n if (channel_hashtag !== null) {\n if (tweet_hashtag_array.includes(channel_hashtag) === false) {\n tweet_hashtag_array.push(channel_hashtag);\n }\n }\n }\n\n return tweet_hashtag_array.join(' ');\n },\n\n // ツイートを送信する\n async sendTweet() {\n\n // 送信中フラグを立てる (重複送信防止)\n if (this.is_tweet_sending === true) {\n return;\n }\n this.is_tweet_sending = true;\n\n // ハッシュタグを整形\n this.tweet_hashtag = this.formatHashtag(this.tweet_hashtag);\n this.updateTweetLetterCount();\n const tweet_hashtag = this.tweet_hashtag;\n\n // 実際に送るツイート本文を作成\n let tweet_text = this.tweet_text;\n if (tweet_hashtag !== '') { // ハッシュタグが入力されているときのみ\n switch (this.settingsStore.settings.tweet_hashtag_position) {\n // ツイート本文の前に追加する\n case 'Prepend': {\n tweet_text = `${tweet_hashtag} ${this.tweet_text}`;\n break;\n }\n // ツイート本文の後に追加する\n case 'Append': {\n tweet_text = `${this.tweet_text} ${tweet_hashtag}`;\n break;\n }\n // ツイート本文の前に追加してから改行する\n case 'PrependWithLineBreak': {\n tweet_text = `${tweet_hashtag}\\n${this.tweet_text}`;\n break;\n }\n // ツイート本文の後に改行してから追加する\n case 'AppendWithLineBreak': {\n tweet_text = `${this.tweet_text}\\n${tweet_hashtag}`;\n break;\n }\n }\n }\n\n // キャプチャへの透かしの描画がオンの場合、キャプチャの Blob を透かし付きのものに差し替える\n const new_tweet_captures: Blob[] = [];\n for (let tweet_capture of this.tweet_captures) {\n if (this.settingsStore.settings.tweet_capture_watermark_position !== 'None') {\n tweet_capture = await this.drawProgramTitleOnCapture(tweet_capture);\n }\n new_tweet_captures.push(tweet_capture);\n }\n\n // ツイート送信 API にリクエスト\n // レスポンスは待たない\n Twitter.sendTweet(this.selected_twitter_account.screen_name, tweet_text, new_tweet_captures).then((result) => {\n this.player.notice(result.message);\n });\n\n // 連投防止のため、フォーム上のツイート本文・キャプチャの選択・キャプチャのフォーカスを消去\n // 送信した感を出す意味合いもある\n for (const capture of this.captures) {\n capture.selected = false;\n capture.focused = false;\n }\n this.tweet_captures = [];\n this.tweet_text = '';\n\n // 送信中フラグを下ろす\n this.is_tweet_sending = false;\n\n // パネルを閉じるように親コンポーネントに伝える\n if (this.settingsStore.settings.fold_panel_after_sending_tweet === true) {\n this.$emit('panel_folding_requested');\n (this.$refs.tweet_text as HTMLTextAreaElement).blur(); // フォーカスを外す\n }\n },\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Twitter.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Twitter.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Twitter.vue?vue&type=template&id=62dbc198&scoped=true&\"\nimport script from \"./Twitter.vue?vue&type=script&lang=ts&\"\nexport * from \"./Twitter.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Twitter.vue?vue&type=style&index=0&id=62dbc198&prod&lang=scss&\"\nimport style1 from \"./Twitter.vue?vue&type=style&index=1&id=62dbc198&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"62dbc198\",\n null\n \n)\n\nexport default component.exports","\nimport Message from '@/message';\nimport APIClient from '@/services/APIClient';\n\n\nclass Captures {\n\n /**\n * キャプチャをサーバーにアップロードし保存する\n * @param blob キャプチャ画像の Blob\n * @param filename サーバーに保存するときのファイル名\n */\n static async uploadCapture(blob: Blob, filename: string): Promise {\n\n // キャプチャ画像の File オブジェクト (= Blob) を FormData に入れる\n // multipart/form-data で送るために必要\n // ref: https://r17n.page/2020/02/04/nodejs-axios-file-upload-api/\n const form_data = new FormData();\n form_data.append('image', blob, filename);\n\n // API リクエストを実行\n const response = await APIClient.post('/captures', form_data, {headers: {'Content-Type': 'multipart/form-data'}});\n\n // エラー処理\n if ('is_error' in response) {\n switch (response.error.message) {\n case 'Permission denied to save the file': {\n Message.error('キャプチャのアップロードに失敗しました。保存先フォルダに書き込み権限がありません。');\n break;\n }\n case 'No space left on the device': {\n Message.error('キャプチャのアップロードに失敗しました。保存先フォルダに空き容量がありません。');\n break;\n }\n case 'Unexpected error occurred while saving the file': {\n Message.error('キャプチャのアップロードに失敗しました。保存中に予期しないエラーが発生しました。');\n break;\n }\n default: {\n APIClient.showGenericError(response, 'キャプチャのアップロードに失敗しました。');\n break;\n }\n }\n return;\n }\n }\n\n // TODO: キャプチャリスト機能の実装時にいろいろ追加する\n}\n\nexport default Captures;\n","\nimport { Buffer } from 'buffer';\n\nimport { convertBlobToPng, copyBlobToClipboard } from 'copy-image-clipboard';\nimport dayjs from 'dayjs';\nimport DPlayer from 'dplayer';\nimport 'dayjs/locale/ja';\nimport * as piexif from 'piexifjs';\n\nimport APIClient from '@/services/APIClient';\nimport Captures from '@/services/Captures';\nimport useChannelsStore from '@/store/ChannelsStore';\nimport useSettingsStore from '@/store/SettingsStore';\nimport Utils from '@/utils';\n\n\n// キャプチャに書き込む EXIF メタデータのインターフェイス\ninterface ICaptureExifData {\n captured_at: string;\n captured_playback_position: number;\n network_id: number;\n service_id: number;\n event_id: number;\n title: string;\n description: string;\n start_time: string;\n end_time: string;\n duration: number;\n caption_text: string | null;\n is_caption_composited: boolean;\n is_comment_composited: boolean;\n}\n\n// CaptureHandler.setEXIFDataToCapture() のオプションのインターフェイス\ninterface ISetEXIFDataToCaptureOptions {\n network_id: number;\n service_id: number;\n event_id: number;\n title: string;\n description: string;\n start_time: string;\n end_time: string;\n duration: number;\n caption_text: string | null;\n is_caption_composited: boolean;\n is_comment_composited: boolean;\n}\n\n// Web フォントを Base64 化したデータ (コメントを SVG の foreignObject としてレンダリングする際に必要)\nlet web_font_noto_sans_base64: string | null = null;\nlet web_font_open_sans_base64: string | null = null;\n\n\nclass CaptureHandler {\n\n private player: DPlayer;\n private player_container: HTMLElement;\n private captured_callback: (blob: Blob, filename: string) => void;\n private capture_button: HTMLDivElement;\n private comment_capture_button: HTMLDivElement;\n private canvas: OffscreenCanvas | HTMLCanvasElement;\n private canvas_context: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;\n private settings_store = useSettingsStore();\n\n constructor(player: DPlayer, captured_callback: (blob: Blob, filename: string) => void) {\n\n this.player = player;\n this.player_container = this.player.container;\n this.captured_callback = captured_callback;\n\n // コメント付きキャプチャボタンの HTML を追加\n // insertAdjacentHTML で .dplayer-icons-right の一番左側に配置する\n // この後に通常のキャプチャボタンが insert されるので、実際は左から2番目\n // TODO: ボタンのデザインをコメント付きだと分かるようなものに変更する\n this.player_container.querySelector('.dplayer-icons.dplayer-icons-right').insertAdjacentHTML('afterbegin', `\n
\n \n \n \n
\n `);\n\n // キャプチャボタンの HTML を追加\n // 標準のスクリーンショット機能は貧弱なので、あえて独自に実装している(そのほうが自由度も高くてやりやすい)\n // insertAdjacentHTML で .dplayer-icons-right の一番左側に配置する\n this.player_container.querySelector('.dplayer-icons.dplayer-icons-right').insertAdjacentHTML('afterbegin', `\n
\n \n \n \n
\n `);\n\n this.comment_capture_button = this.player_container.querySelector('.dplayer-comment-capture-icon');\n this.capture_button = this.player_container.querySelector('.dplayer-capture-icon');\n\n // キャプチャ用の Canvas を初期化\n // パフォーマンス向上のため、一度作成した Canvas は使い回す\n // OffscreenCanvas が使えるなら使う (OffscreenCanvas の方がパフォーマンスが良い)\n this.canvas = ('OffscreenCanvas' in window) ? new OffscreenCanvas(0, 0) : document.createElement('canvas');\n this.canvas_context = this.canvas.getContext('2d', {\n alpha: false,\n desynchronized: true,\n willReadFrequently: false,\n }) as OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;\n\n // 映像の解像度を Canvas サイズとして設定\n // 映像が読み込まれた / 画質が変わった際に Canvas のサイズを映像のサイズに合わせる\n this.canvas.width = 0;\n this.canvas.height = 0;\n player.on('loadedmetadata', async () => {\n this.canvas.width = player.video.videoWidth;\n this.canvas.height = player.video.videoHeight;\n // 映像サイズがちゃんと設定されるまで繰り返す (Safari 対策)\n while (this.canvas.width === 0 && this.canvas.height === 0) {\n await Utils.sleep(0.1);\n this.canvas.width = player.video.videoWidth;\n this.canvas.height = player.video.videoHeight;\n }\n });\n\n // もし Web フォントがダウンロードされていないならダウンロード\n // コメントのレンダリングに最低限必要なウェイトのみダウンロードする\n // 待つ必要はないので非同期で実行\n // ref: https://stackoverflow.com/a/66969479/17124142\n (async () => {\n const web_font_noto_sans_url = 'https://cdn.jsdelivr.net/npm/noto-sans-japanese@1.0.0/fonts/NotoSansJP-Bold.woff2';\n const web_font_open_sans_url = 'https://cdn.jsdelivr.net/npm/open-sans-all@0.1.3/fonts/open-sans-700.woff2';\n const base64_font_prefix = 'data:font/woff2;base64,';\n if (web_font_noto_sans_base64 === null) {\n const web_font_noto_sans: ArrayBuffer = (await APIClient.get(web_font_noto_sans_url, {\n responseType: 'arraybuffer',\n })).data;\n // Buffer で受け取ったデータを Base64 に変換\n web_font_noto_sans_base64 = base64_font_prefix + Buffer.from(web_font_noto_sans).toString('base64');\n }\n if (web_font_open_sans_base64 === null) {\n const web_font_open_sans: ArrayBuffer = (await APIClient.get(web_font_open_sans_url, {\n responseType: 'arraybuffer',\n })).data;\n // Buffer で受け取ったデータを Base64 に変換\n web_font_open_sans_base64 = base64_font_prefix + Buffer.from(web_font_open_sans).toString('base64');\n }\n })();\n }\n\n\n /**\n * 映像をキャプチャして保存する\n * 映像のみと字幕付き (字幕表示時のみ) の両方のキャプチャを生成できる\n * @param with_comments キャプチャにコメントを合成するかどうか\n */\n public async captureAndSave(with_comments: boolean): Promise {\n\n const total_time = Utils.time();\n\n // チャンネル情報を取得 (ライブ視聴画面のみ、ビデオ視聴画面では null になる)\n const channels_store = useChannelsStore();\n const channel = channels_store.is_showing_live ? channels_store.channel.current : null;\n\n // ***** バリデーション *****\n\n // ラジオチャンネルを視聴している場合 (当然映像がないのでキャプチャできない)\n if (channel !== null && channel.is_radiochannel === true) {\n this.player.notice('ラジオチャンネルはキャプチャできません。');\n return;\n }\n\n // まだ映像の表示準備が終わっていない (Canvas の幅/高さが 0 のまま)\n if (this.canvas.width === 0 && this.canvas.height === 0) {\n this.player.notice('読み込み中はキャプチャできません。');\n return;\n }\n\n // コメントが表示されていないのにコメント付きキャプチャしようとした\n if (with_comments === true && this.player.danmaku.showing === false) {\n this.player.notice('コメントを付けてキャプチャするには、コメント表示をオンにしてください。');\n return;\n }\n\n // ***** キャプチャの下準備 *****\n\n // キャプチャ中はキャプチャボタンをハイライトする\n this.addHighlight(with_comments);\n\n // ファイル名(拡張子なし)\n // TODO: ファイル名パターンを変更できるようにする\n const filename_base = `Capture_${dayjs().format('YYYYMMDD-HHmmss')}`;\n const filename = `${filename_base}.jpg`; // 字幕なしキャプチャ\n const filename_caption = `${filename_base}_caption.jpg`; // 字幕ありキャプチャ\n\n // 字幕・文字スーパーの Canvas を取得\n // getRawCanvas() で映像と同じ解像度の Canvas が取得できる\n const caption_canvas: HTMLCanvasElement = this.player.plugins.aribb24Caption.getRawCanvas();\n const superimpose_canvas: HTMLCanvasElement = this.player.plugins.aribb24Superimpose.getRawCanvas();\n\n // 字幕が表示されているか\n // @ts-ignore\n const is_caption_showing = (this.player.plugins.aribb24Caption.isShowing === true &&\n this.player.plugins.aribb24Caption.isPresent());\n\n // 文字スーパーが表示されているか\n // @ts-ignore\n const is_superimpose_showing = (this.player.plugins.aribb24Superimpose.isShowing === true &&\n this.player.plugins.aribb24Superimpose.isPresent());\n\n // 字幕が表示されている場合、表示中の字幕のテキストを取得\n // 取得した字幕のテキストは、キャプチャに字幕が合成されているかに関わらず、常に EXIF メタデータに書き込まれる\n // 字幕が表示されていない場合は null を入れ、キャプチャしたシーンで字幕が表示されていなかったことを明示する\n const caption_text = is_caption_showing ? this.player.plugins.aribb24Caption.getTextContent() : null;\n\n // EXIF に書き込むメタデータを取得する\n // ライブ視聴画面では、番組情報から EXIF に書き込むメタデータを取得する\n let exif_options: ISetEXIFDataToCaptureOptions;\n if (channel !== null) {\n exif_options = {\n network_id: channel.network_id,\n service_id: channel.service_id,\n event_id: channel.program_present?.event_id ?? -1,\n title: channel.program_present?.title ?? '放送休止',\n description: channel.program_present?.description ?? '',\n start_time: channel.program_present?.start_time ?? '2000-01-01T00:00:00+09:00',\n end_time: channel.program_present?.end_time ?? '2000-01-01T00:00:00+09:00',\n duration: channel.program_present?.duration ?? 0,\n caption_text: caption_text,\n is_caption_composited: false, // 後で上書きされる\n is_comment_composited: false, // 後で上書きされる\n };\n // ビデオ視聴画面では、録画番組情報から EXIF に書き込むメタデータを取得する\n } else {\n // TODO\n }\n\n // エクスポートして保存する共通処理\n const export_and_save = async (\n canvas: OffscreenCanvas | HTMLCanvasElement,\n filename: string,\n exif_options: ISetEXIFDataToCaptureOptions,\n ): Promise => {\n\n // Canvas を Blob にエクスポート\n const time = Utils.time();\n let blob: Blob;\n try {\n blob = await this.exportToBlob(canvas);\n } catch (error) {\n console.log(error);\n this.player.notice('キャプチャの保存に失敗しました…');\n return false;\n }\n console.log('[CaptureHandler] Export to Blob:', Utils.mathFloor(Utils.time() - time, 3), 'sec');\n\n // キャプチャに番組情報などのメタデータ (EXIF) をセット\n blob = await this.setEXIFDataToCapture(blob, exif_options);\n\n // キャプチャの保存先: ブラウザでダウンロード or 両方\n if (['Browser', 'Both'].includes(this.settings_store.settings.capture_save_mode)) {\n Utils.downloadBlobData(blob, filename);\n }\n\n // キャプチャの保存先: KonomiTV サーバーにアップロード or 両方\n // 時間がかかるし完了を待つ必要がないので非同期\n if (['UploadServer', 'Both'].includes(this.settings_store.settings.capture_save_mode)) {\n Captures.uploadCapture(blob, filename);\n }\n\n return blob;\n };\n\n // ***** 映像のキャプチャ *****\n\n // null はまだキャプチャしていないことを、false はキャプチャに失敗したことを表す\n let capture_normal: {blob: Blob, filename: string} | null | false = null;\n let capture_caption: {blob: Blob, filename: string} | null | false = null;\n\n // 映像の ImageBitmap を取得\n const image_bitmap = await createImageBitmap(this.player.video);\n\n // もし映像以外に追加で合成するものがないなら、処理の高速化のために ImageBitmap をそのまま Canvas に転送して Blob 化する\n // コメントキャプチャではない & 文字スーパーが表示されていない (=合成処理を行う必要がない) &\n // (字幕が表示されていない or 字幕が表示されているが合成しないように設定されている) 場合\n // コメント付きキャプチャではなく、かつ字幕のない番組では大半がここの処理を通ることになる\n if (with_comments === false && is_superimpose_showing === false &&\n (is_caption_showing === false || this.settings_store.settings.capture_caption_mode === 'VideoOnly')) {\n\n // OffscreenCanvas が使えるなら使う (OffscreenCanvas の方がパフォーマンスが良い)\n const bitmap_canvas = ('OffscreenCanvas' in window) ?\n new OffscreenCanvas(image_bitmap.width, image_bitmap.height) : document.createElement('canvas');\n bitmap_canvas.width = image_bitmap.width;\n bitmap_canvas.height = image_bitmap.height;\n const canvas_context = bitmap_canvas.getContext('bitmaprenderer', {alpha: false}) as ImageBitmapRenderingContext;\n\n // Canvas に映像がキャプチャされた ImageBitmap を転送\n // 描画ではなくゼロコピーで転送しているらしい…?\n canvas_context.transferFromImageBitmap(image_bitmap);\n image_bitmap.close(); // 今後使うことはないので明示的に閉じる\n\n // ファイル名\n // 保存モードが「字幕キャプチャのみ」のとき (=字幕キャプチャのみをキャプチャする設定にしていたが、字幕がそもそもないとき) は、\n // 便宜上字幕ありキャプチャと同じファイル名で保存する\n const filename_real =\n (this.settings_store.settings.capture_caption_mode === 'CompositingCaption') ? filename_caption : filename;\n\n // Blob にエクスポートして保存\n // false が返ってきた場合は失敗を意味する\n const blob = await export_and_save(bitmap_canvas, filename_real, {\n ...exif_options,\n is_caption_composited: false,\n is_comment_composited: false,\n });\n if (blob !== false) {\n capture_normal = {blob: blob, filename: filename_real};\n } else {\n capture_normal = false; // キャプチャのエクスポートに失敗\n }\n\n // キャプチャの Blob をコールバック関数に渡す\n // ここでコールバック関数に渡した Blob が Twitter タブのキャプチャリストに送られる\n if (capture_normal !== false) {\n this.captured_callback(capture_normal.blob, capture_normal.filename);\n }\n\n // ***** 通常実行 (Canvas にキャプチャ以外のデータを重ねて描画する必要があるケース) *****\n } else {\n\n const promises: Promise[] = [];\n\n // Canvas に映像がキャプチャされた ImageBitmap を描画\n this.canvas_context.drawImage(image_bitmap, 0, 0, this.canvas.width, this.canvas.height);\n\n // 文字スーパーを描画 (表示されている場合)\n // 文字スーパー自体が稀だし、文字スーパーなしでキャプチャ撮りたいユースケースはない…はず\n if (is_superimpose_showing === true) {\n this.canvas_context.drawImage(superimpose_canvas, 0, 0, this.canvas.width, this.canvas.height);\n }\n\n // コメント付きキャプチャ: 追加でニコニコ実況のコメントを描画\n let comments_image: HTMLImageElement | null = null;\n if (with_comments === true) {\n comments_image = await this.createCommentsImage();\n await this.drawComments(comments_image);\n }\n\n // ***** 映像のみのキャプチャを保存 *****\n\n // 字幕表示時のキャプチャの保存モード: 映像のみ or 両方\n // 保存モードが「字幕キャプチャのみ」になっているが字幕が表示されていない場合も実行する\n if (['VideoOnly', 'Both'].includes(this.settings_store.settings.capture_caption_mode) || is_caption_showing === false) {\n\n promises.push((async () => {\n\n // ファイル名\n // 保存モードが「字幕キャプチャのみ」のとき (=字幕キャプチャのみをキャプチャする設定にしていたが、字幕がそもそもないとき) は、\n // 便宜上字幕ありキャプチャと同じファイル名で保存する\n const filename_real =\n (this.settings_store.settings.capture_caption_mode === 'CompositingCaption') ? filename_caption : filename;\n\n // Blob にエクスポートして保存\n const blob = await export_and_save(this.canvas, filename_real, {\n ...exif_options,\n is_caption_composited: false,\n is_comment_composited: with_comments,\n });\n if (blob !== false) {\n capture_normal = {blob: blob, filename: filename_real};\n } else {\n capture_normal = false; // キャプチャのエクスポートに失敗\n }\n\n // キャプチャの Blob をコールバック関数に渡す\n // ここでコールバック関数に渡した Blob が Twitter タブのキャプチャリストに送られる\n if (capture_normal !== false) {\n this.captured_callback(capture_normal.blob, capture_normal.filename);\n }\n\n })());\n }\n\n // ***** 字幕付きのキャプチャを保存 *****\n\n // 字幕表示時のキャプチャの保存モード: 字幕キャプチャのみ or 両方\n // 字幕が表示されているときのみ実行(字幕が表示されていないのにやっても意味がない)\n if (['CompositingCaption', 'Both'].includes(this.settings_store.settings.capture_caption_mode) && is_caption_showing === true) {\n\n promises.push((async () => {\n\n // コメント付きキャプチャ: 映像と文字スーパーの描画をやり直す\n // すでに字幕なしキャプチャを生成する過程でコメントを描画してしまっているため、映像描画からやり直す必要がある\n if (with_comments === true) {\n this.canvas_context.drawImage(image_bitmap, 0, 0, this.canvas.width, this.canvas.height);\n if (is_superimpose_showing === true) {\n this.canvas_context.drawImage(superimpose_canvas, 0, 0, this.canvas.width, this.canvas.height);\n }\n }\n image_bitmap.close(); // 今後使うことはないので明示的に閉じる\n\n // 字幕を重ねて描画\n this.canvas_context.drawImage(caption_canvas, 0, 0, this.canvas.width, this.canvas.height);\n\n // コメント付きキャプチャ: 追加でニコニコ実況のコメントを描画\n if (with_comments === true) {\n await this.drawComments(comments_image);\n }\n\n // Blob にエクスポートして保存\n const blob = await export_and_save(this.canvas, filename_caption, {\n ...exif_options,\n is_caption_composited: true,\n is_comment_composited: with_comments,\n });\n if (blob !== false) {\n capture_caption = {blob: blob, filename: filename_caption};\n } else {\n capture_caption = false; // キャプチャのエクスポートに失敗\n }\n\n // キャプチャの Blob をコールバック関数に渡す\n // ここでコールバック関数に渡した Blob が Twitter タブのキャプチャリストに送られる\n if (capture_caption !== false) {\n // 字幕表示時のキャプチャの保存モードが「両方 (Both)」のときのみ、映像のみのキャプチャの生成が終わるまで待ってから実行\n // 必ずキャプチャリストへの追加が [映像のみ] → [字幕付き] の順序で行われるようにする\n if (this.settings_store.settings.capture_caption_mode === 'Both') {\n while (capture_normal === null) {\n // Blob (成功) か false (失敗) が capture_normal に入るまでループ\n await Utils.sleep(0.01);\n }\n }\n this.captured_callback(capture_caption.blob, capture_caption.filename);\n }\n\n })());\n }\n\n // すべてのキャプチャ処理が終わるまで待つ\n await Promise.all(promises);\n }\n\n console.log('[CaptureHandler] Total:', Utils.mathFloor(Utils.time() - total_time, 3), 'sec');\n\n // キャプチャボタンのハイライトを削除する\n this.removeHighlight(with_comments);\n\n // Twitter タブのキャプチャリストに送る処理が最優先なので、コールバックを実行しきった後に時間のかかるクリップボードへのコピーを行う\n for (const capture of [capture_normal, capture_caption]) {\n\n // クリップボードへのコピーが有効なら、キャプチャの Blob をクリップボードにコピー\n // PNG 以外は受け付けないそうなので、JPEG を PNG に変換してからコピーしている\n if (this.settings_store.settings.capture_copy_to_clipboard && capture !== null && capture !== false) {\n try {\n await copyBlobToClipboard(await convertBlobToPng(capture.blob));\n } catch (error) {\n this.player.notice('クリップボードへのキャプチャのコピーに失敗しました…');\n console.error(error);\n }\n }\n }\n }\n\n\n /**\n * キャプチャボタンをハイライトする\n * @param with_comments コメント付きキャプチャボタンをハイライトするか\n */\n private addHighlight(with_comments: boolean = false): void {\n if (with_comments) {\n this.comment_capture_button.classList.add('dplayer-capturing');\n } else {\n this.capture_button.classList.add('dplayer-capturing');\n }\n }\n\n\n /**\n * キャプチャボタンのハイライトを外す\n * @param with_comments コメント付きキャプチャボタンのハイライトを外すか\n */\n private removeHighlight(with_comments: boolean = false): void {\n if (with_comments) {\n this.comment_capture_button.classList.remove('dplayer-capturing');\n } else {\n this.capture_button.classList.remove('dplayer-capturing');\n }\n }\n\n\n /**\n * DPlayer から取得したコメント HTML を SVG 画像の HTMLImageElement に変換する\n * ZenzaWatch のコードを参考にしている\n * ref: https://github.com/segabito/ZenzaWatch/blob/master/packages/lib/src/dom/VideoCaptureUtil.js\n * ref: https://web.archive.org/web/2/https://developer.mozilla.org/ja/docs/Web/HTML/Canvas/Drawing_DOM_objects_into_a_canvas\n * @param html DPlayer から取得したコメント HTML\n * @param width SVG 画像の幅\n * @param height SVG 画像の高さ\n * @returns SVG 画像の HTMLImageElement\n */\n private async commentsHTMLtoSVGImage(html: string, width: number, height: number): Promise {\n\n // SVG の foreignObject を使い、HTML をそのまま SVG に埋め込む\n // SVG なので、CSS はインラインでないと適用されない…\n // DPlayer の danmaku.scss の内容のうち、描画に必要なプロパティのみを列挙 (追加変更したものもある)\n // ref: https://github.com/tsukumijima/DPlayer/blob/master/src/css/danmaku.scss\n const svg = (`\n \n \n
\n \n ${html}\n
\n
\n
\n `).trim();\n\n // Data URL 化して Image オブジェクトにする\n // わざわざ Blob にするよりこっちのほうが楽\n const image = new Image();\n image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n\n // Image は onload を使わなくても await Image.decode() でロードできる\n await image.decode();\n return image;\n }\n\n\n /**\n * DPlayer から表示中のコメントを取得し、SVG 画像の HTMLImageElement を作成する\n * @returns 表示されているコメントが描画された HTMLImageElement\n */\n private async createCommentsImage(): Promise {\n\n // コメントが表示されている要素の HTML を取得する\n let comments_html = this.player.template.danmaku.outerHTML;\n\n // HTML を取得するだけではスクロール中コメントの表示位置が特定できないため、HTML を修正する\n for (const comment of this.player_container.querySelectorAll('.dplayer-danmaku-move')) { // コメントの数だけ置換\n // スクロール中のコメントの表示座標を計算\n const position = comment.getBoundingClientRect().left - this.player.video.getBoundingClientRect().left;\n comments_html = comments_html.replace(/transform: translateX\\(.*?\\);/, `left: ${position}px;`)\n .replaceAll('border: 2px solid #E64F97;', '');\n }\n\n // HTML を画像として取得\n // SVG のサイズはコメントが表示されている要素に合わせる (そうしないとプレイヤー側と一致しない)\n // SVG はベクター画像なので、リサイズしても画質が変わらないはず\n return await this.commentsHTMLtoSVGImage(\n comments_html,\n this.player.template.danmaku.offsetWidth,\n this.player.template.danmaku.offsetHeight,\n );\n }\n\n\n /**\n * 現在表示されているニコニコ実況のコメントを Canvas に描画する\n */\n private async drawComments(comments_image: HTMLImageElement): Promise {\n\n // コメント描画領域がコントロールの表示によりリサイズされている (=16:9でない) 場合も考慮して、コメント要素の offsetWidth から高さを求める\n // 映像の横解像度 (ex: 1920) がコメント描画領域の幅 (ex: 1280) の何倍かの割合 (ex: 1.5 (150%))\n const draw_scale_ratio = this.canvas.width / this.player.template.danmaku.offsetWidth;\n\n // コメント描画領域の高さを映像の横解像度に合わせて(コメント描画領域のアスペクト比を維持したまま)拡大した値\n // 映像の縦解像度が 1080 のとき、コントロールがコメント領域と被っていない or 表示されていないなら、この値は 1080 に近くなる\n const draw_height = this.player.template.danmaku.offsetHeight * draw_scale_ratio;\n\n this.canvas_context.drawImage(comments_image, 0, 0, this.canvas.width, draw_height);\n }\n\n\n /**\n * Canvas もしくは OffscreenCanvas に描画されている画像を Blob に変換する\n * JPEG 画像の品質は 99% にした方が若干 Blob 変換までの速度が速い (?)\n * @param canvas Canvas もしくは OffscreenCanvas\n * @returns Blob 化した画像\n */\n private async exportToBlob(canvas: HTMLCanvasElement | OffscreenCanvas): Promise {\n if ('OffscreenCanvas' in window && canvas instanceof OffscreenCanvas) {\n return await canvas.convertToBlob({type: 'image/jpeg', quality: 0.99});\n } else if (canvas instanceof HTMLCanvasElement) {\n return new Promise((resolve, reject) => {\n canvas.toBlob((blob) => {\n if (blob !== null) {\n resolve(blob);\n } else {\n reject(new Error('Failed to convert canvas to blob'));\n }\n }, 'image/jpeg', 0.99);\n });\n }\n }\n\n\n /**\n * キャプチャ画像に番組情報と撮影時刻、字幕やコメントが合成されているかどうかのメタデータ (EXIF) をセットする\n * @param blob キャプチャ画像の Blob オブジェクト\n * @param options EXIF にセットする番組情報データ・字幕テキスト・字幕が合成されているかどうか・コメントが合成されているかどうか\n * @returns EXIF が追加されたキャプチャ画像の Blob オブジェクト\n */\n private async setEXIFDataToCapture(blob: Blob, options: ISetEXIFDataToCaptureOptions): Promise {\n\n // 番組開始時刻換算のキャプチャ時刻 (秒)\n const captured_playback_position = dayjs().diff(dayjs(options.start_time), 'second', true);\n\n // EXIF の XPComment 領域に入れるメタデータの JSON オブジェクト\n // 撮影時刻とチャンネル・番組を一意に特定できる情報を入れる\n const json: ICaptureExifData = {\n captured_at: dayjs().format('YYYY-MM-DDTHH:mm:ss+09:00'), // ISO8601 フォーマットのキャプチャ時刻\n captured_playback_position: captured_playback_position, // 番組開始時刻換算のキャプチャ時刻 (秒)\n network_id: options.network_id, // 番組が放送されたチャンネルのネットワーク ID\n service_id: options.service_id, // 番組が放送されたチャンネルのサービス ID\n event_id: options.event_id, // 番組のイベント ID\n title: options.title, // 番組タイトル\n description: options.description, // 番組概要\n start_time: options.start_time, // 番組開始時刻 (ISO8601 フォーマット)\n end_time: options.end_time, // 番組終了時刻 (ISO8601 フォーマット)\n duration: options.duration, // 番組長 (秒)\n caption_text: options.caption_text, // 字幕のテキスト (キャプチャした瞬間に字幕が表示されていなかったときは null)\n is_caption_composited: options.is_caption_composited, // 字幕が合成されているか\n is_comment_composited: options.is_comment_composited, // コメントが合成されているか\n };\n\n // 保存する EXIF メタデータを構築\n // ref: 「カメラアプリで体感するWeb App」4.2\n const datetime = dayjs().format('YYYY:MM:DD HH:mm:ss'); // すべてコロンで区切るのがポイント\n const exif: piexif.IExif = {\n '0th': {\n // 必須らしいプロパティ\n // とりあえずデフォルト値 (?) を設定しておく\n [piexif.TagValues.ImageIFD.XResolution]: [72, 1],\n [piexif.TagValues.ImageIFD.YResolution]: [72, 1],\n [piexif.TagValues.ImageIFD.ResolutionUnit]: 2,\n [piexif.TagValues.ImageIFD.YCbCrPositioning]: 1,\n // 撮影時刻\n [piexif.TagValues.ImageIFD.DateTime]: datetime,\n // ソフトウェア名\n [piexif.TagValues.ImageIFD.Software]: `KonomiTV version ${Utils.version}`,\n // Microsoft 拡張のコメント領域(エクスプローラーで出てくるコメント欄と同じもの)\n // ref: https://stackoverflow.com/a/66186660/17124142\n [piexif.TagValues.ImageIFD.XPComment]: [...Buffer.from(JSON.stringify(json), 'ucs2')],\n },\n 'Exif': {\n // 必須らしいプロパティ\n // とりあえずデフォルト値 (?) を設定しておく\n [piexif.TagValues.ExifIFD.ExifVersion]: '0230',\n [piexif.TagValues.ExifIFD.ComponentsConfiguration]: '\\x01\\x02\\x03\\x00',\n [piexif.TagValues.ExifIFD.FlashpixVersion]: '0100',\n [piexif.TagValues.ExifIFD.ColorSpace]: 1,\n // 撮影時刻\n [piexif.TagValues.ExifIFD.DateTimeOriginal]: datetime,\n [piexif.TagValues.ExifIFD.DateTimeDigitized]: datetime,\n },\n };\n const exif_string = piexif.dump(exif); // バイナリ文字列に変換した EXIF データ\n\n // piexifjs はバイナリ文字列か DataURL しか受け付けないので、Blob をバイナリ文字列に変換\n const blob_string: string = await new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(reader.result as string);\n reader.onerror = reject;\n reader.readAsBinaryString(blob); // バイナリ文字列で読み込む\n });\n\n // 画像に EXIF を挿入\n // 戻り値は EXIF が追加された画像のバイナリ文字列 (なぜ未だにバイナリ文字列で実装してるんだ…)\n const blob_string_new = piexif.insert(exif_string, blob_string);\n\n // 画像のバイナリ文字列を ArrayBuffer に変換\n // ref: 「カメラアプリで体感するWeb App」4.2\n const buffer = new Uint8Array(blob_string_new.length);\n for (let index = 0; index < buffer.length; index++) {\n buffer[index] = blob_string_new.charCodeAt(index) & 0xff;\n }\n\n // 新しい Blob を返す\n return new Blob([buffer], {type: blob.type});\n }\n}\n\nexport default CaptureHandler;\n","\n\nimport dayjs from 'dayjs';\nimport DPlayer from 'dplayer';\nimport * as DPlayerType from 'dplayer/dist/d.ts/types/DPlayer';\nimport mpegts from 'mpegts.js';\nimport { mapStores } from 'pinia';\nimport Vue from 'vue';\n\nimport BottomNavigation from '@/components/BottomNavigation.vue';\nimport Channel from '@/components/Panel/Channel.vue';\nimport Comment from '@/components/Panel/Comment.vue';\nimport Program from '@/components/Panel/Program.vue';\nimport Twitter from '@/components/Panel/Twitter.vue';\nimport APIClient from '@/services/APIClient';\nimport { IChannel } from '@/services/Channels';\nimport CaptureHandler from '@/services/player/CaptureHandler';\nimport useChannelsStore from '@/store/ChannelsStore';\nimport useSettingsStore from '@/store/SettingsStore';\nimport Utils, { PlayerUtils, ProgramUtils } from '@/utils';\n\n// 低遅延モードオン時の再生バッファ (秒単位)\n// 0.7 秒程度余裕を持たせる\nconst PLAYBACK_BUFFER_SEC_LOW_LATENCY = 0.7;\n\n// 低遅延モードオフ時の再生バッファ (秒単位)\n// 5秒程度の遅延を許容する\nconst PLAYBACK_BUFFER_SEC = 5.0;\n\nexport default Vue.extend({\n name: 'TV-Watch',\n components: {\n BottomNavigation,\n Channel,\n Comment,\n Program,\n Twitter,\n },\n data() {\n return {\n\n // ユーティリティをテンプレートで使えるように\n Utils: Utils,\n ProgramUtils: ProgramUtils,\n\n // 現在時刻\n time: dayjs().format('YYYY/MM/DD HH:mm:ss'),\n\n // 表示されるパネルのタブ\n tv_panel_active_tab: useSettingsStore().settings.tv_panel_active_tab,\n\n // 背景の URL\n background_url: '',\n\n // プレイヤーのローディング状態\n // 既定でローディングとする\n is_loading: true,\n\n // プレイヤーが映像の再生をバッファリングしているか\n // 視聴開始時以外にも、ネットワークが遅くて再生が一時的に途切れたときなどで表示される\n // 既定でバッファリング中とする\n is_video_buffering: true,\n\n // プレイヤーの背景を表示するか\n // 既定で表示しない\n is_background_display: false,\n\n // コントロールを表示するか\n // 既定で表示する\n is_control_display: true,\n\n // パネルを表示するか\n // panel_display_state が 'AlwaysDisplay' なら常に表示し、'AlwaysFold' なら常に折りたたむ\n // 'RestorePreviousState' なら showed_panel_last_time の値を使い、前回の状態を復元する\n is_panel_display: (() => {\n const settings_store = useSettingsStore();\n switch (settings_store.settings.panel_display_state) {\n case 'AlwaysDisplay':\n return true;\n case 'AlwaysFold':\n return false;\n case 'RestorePreviousState':\n return settings_store.settings.showed_panel_last_time;\n }\n })() as boolean,\n\n // フルスクリーン状態かどうか\n is_fullscreen: false,\n\n // IME 変換中かどうか\n is_ime_composing: false,\n\n // 仮想キーボードが表示されているか\n is_virtual_keyboard_display: false,\n\n // プレイヤーからのコメント送信から間もないかどうか\n is_comment_send_just_did: false,\n\n // インターバル ID\n // ページ遷移時に setInterval(), setTimeout() の実行を止めるのに使う\n // setInterval(), setTimeout() の返り値を登録する\n interval_ids: [] as number[],\n\n // コントロール表示切り替え用のインターバル ID\n // 混ぜるとダメなので独立させる\n control_interval_id: 0,\n\n // ***** チャンネル *****\n\n // ザッピング(「前/次のチャンネル」ボタン or 上下キーショートカット)によるチャンネル移動かどうか\n is_zapping: false,\n\n // ザッピングで連続してチャンネルを切り替えている最中かどうか\n // 「連続して」とは、切り替える間隔が 0.5 秒以下で、再生セッションが初期化される前に次のチャンネルに切り替えたときのこと\n is_zapping_continuously: false,\n\n // ***** プレイヤー *****\n\n // プレイヤー (DPlayer) のインスタンス\n player: null as DPlayer | null,\n\n // プレイヤーの破棄を許可するかどうか\n player_can_be_destroyed: false,\n\n // mpegts.js がサポートされているかどうか\n // mpegts.js がサポートされていない場合は LL-HLS にフォールバックする (基本 iPhone Safari 向け)\n is_mpegts_supported: mpegts.isSupported() === true,\n\n // RomSound の AudioContext\n romsounds_context: null as AudioContext | null,\n\n // RomSound の AudioBuffer(音声データ)が入るリスト\n romsounds_buffers: [] as AudioBuffer[],\n\n // イベントソースのインスタンス\n eventsource: null as EventSource | null,\n\n // フルスクリーン状態が切り替わったときのハンドラー\n fullscreen_handler: null as () => void | null,\n\n // キャプチャハンドラーのインスタンス\n capture_handler: null as CaptureHandler | null,\n\n // ***** キーボードショートカット *****\n\n // ショートカットキーのハンドラー\n shortcut_key_handler: null as (event: KeyboardEvent) => void | null,\n\n // ショートカットキーの最終押下時刻のタイムスタンプ\n shortcut_key_pressed_at: Utils.time(),\n\n // キーボードショートカットの一覧のモーダルを表示するか\n shortcut_key_modal: false,\n\n // キーボードショートカットの一覧に表示するショートカットキーのリスト\n shortcut_key_list: {\n left_column: [\n {\n name: '全般',\n icon: 'fluent:home-20-filled',\n icon_height: '22px',\n shortcuts: [\n { name: '数字キー/テンキーに対応するリモコン番号 (1~12) の地デジチャンネルに切り替える', keys: [{name: '1~9, 0, -(=), ^(~)', icon: false}] },\n { name: '数字キー/テンキーに対応するリモコン番号 (1~12) の BS チャンネルに切り替える', keys: [{name: 'Shift', icon: false}, {name: '1~9, 0, -(=), ^(~)', icon: false}] },\n { name: '前のチャンネルに切り替える', keys: [{name: 'fluent:arrow-up-12-filled', icon: true}] },\n { name: '次のチャンネルに切り替える', keys: [{name: 'fluent:arrow-down-12-filled', icon: true}] },\n { name: 'キーボードショートカットの一覧を表示する', keys: [{name: '/(?)', icon: false}] },\n ]\n },\n {\n name: 'プレイヤー',\n icon: 'fluent:play-20-filled',\n icon_height: '20px',\n shortcuts: [\n { name: '再生 / 一時停止の切り替え', keys: [{name: 'Space', icon: false}] },\n { name: '再生 / 一時停止の切り替え (キャプチャタブ表示時)', keys: [{name: 'Shift', icon: false}, {name: 'Space', icon: false}] },\n { name: 'プレイヤーの音量を上げる', keys: [{name: Utils.CtrlOrCmd(), icon: false}, {name: 'fluent:arrow-up-12-filled', icon: true}] },\n { name: 'プレイヤーの音量を下げる', keys: [{name: Utils.CtrlOrCmd(), icon: false}, {name: 'fluent:arrow-down-12-filled', icon: true}] },\n { name: '停止して0.5秒早戻し', keys: [{name: Utils.CtrlOrCmd(), icon: false}, {name: 'fluent:arrow-left-12-filled', icon: true}] },\n { name: '停止して0.5秒早送り', keys: [{name: Utils.CtrlOrCmd(), icon: false}, {name: 'fluent:arrow-right-12-filled', icon: true}] },\n { name: 'フルスクリーンの切り替え', keys: [{name: 'F', icon: false}] },\n { name: 'ライブストリームの同期', keys: [{name: 'W', icon: false}] },\n { name: 'Picture-in-Picture の表示切り替え', keys: [{name: 'E', icon: false}] },\n { name: '字幕の表示切り替え', keys: [{name: 'S', icon: false}] },\n { name: 'コメントの表示切り替え', keys: [{name: 'D', icon: false}] },\n { name: '映像をキャプチャする', keys: [{name: 'C', icon: false}] },\n { name: '映像をコメントを付けてキャプチャする', keys: [{name: 'V', icon: false}] },\n { name: 'コメント入力フォームにフォーカスする', keys: [{name: 'M', icon: false}] },\n { name: 'コメント入力フォームを閉じる', keys: [{name: Utils.CtrlOrCmd(), icon: false}, {name: 'M', icon: false}] },\n ]\n },\n ],\n right_column: [\n {\n name: 'パネル',\n icon: 'fluent:panel-right-20-filled',\n icon_height: '24px',\n shortcuts: [\n { name: 'パネルの表示切り替え', keys: [{name: 'P', icon: false}] },\n { name: '番組情報タブを表示する', keys: [{name: 'K', icon: false}] },\n { name: 'チャンネルタブを表示する', keys: [{name: 'L', icon: false}] },\n { name: 'コメントタブを表示する', keys: [{name: ';(+)', icon: false}] },\n { name: 'Twitter タブを表示する', keys: [{name: ':(*)', icon: false}] },\n ]\n },\n {\n name: 'Twitter',\n icon: 'fa-brands:twitter',\n icon_height: '22px',\n shortcuts: [\n { name: 'ツイート検索タブを表示する', keys: [{name: '[ (「)', icon: false}] },\n { name: 'タイムラインタブを表示する', keys: [{name: '] (」)', icon: false}] },\n { name: 'キャプチャタブを表示する', keys: [{name: '\(¥)', icon: false}] },\n { name: 'ツイート入力フォームにフォーカスを当てる/フォーカスを外す', keys: [{name: 'Tab', icon: false}] },\n { name: 'キャプチャにフォーカスする', keys: [{name: 'キャプチャタブを表示', icon: false}, {name: 'fluent:arrow-up-12-filled;fluent:arrow-down-12-filled;fluent:arrow-left-12-filled;fluent:arrow-right-12-filled', icon: true}] },\n { name: 'キャプチャを拡大表示する/
キャプチャの拡大表示を閉じる', keys: [{name: 'キャプチャにフォーカス', icon: false}, {name: 'Enter', icon: false}] },\n { name: 'キャプチャを選択する/
キャプチャの選択を解除する', keys: [{name: 'キャプチャにフォーカス', icon: false}, {name: 'Space', icon: false}] },\n { name: 'クリップボード内の画像を
キャプチャとして取り込む', keys: [{name: 'ツイート入力
フォームにフォーカス', icon: false}, {name: Utils.CtrlOrCmd(), icon: false}, {name: 'V', icon: false}] },\n { name: 'ツイートを送信する', keys: [{name: 'Twitter タブを表示', icon: false}, {name: Utils.CtrlOrCmd(), icon: false}, {name: 'Enter', icon: false}] },\n ]\n },\n ]\n }\n };\n },\n computed: {\n // ChannelsStore / SettingsStore に this.channelsStore / this.settingsStore でアクセスできるようにする\n // ref: https://pinia.vuejs.org/cookbook/options-api.html\n ...mapStores(useChannelsStore, useSettingsStore),\n },\n // 開始時に実行\n async created() {\n\n // チャンネル選局のキーボードショートカットを Alt or Option + 数字キー/テンキーに変更する設定が有効なら、\n // キーボードショートカット一覧に反映する\n if (this.settingsStore.settings.tv_channel_selection_requires_alt_key === true) {\n this.shortcut_key_list.left_column[0].shortcuts[0].keys.unshift({name: Utils.AltOrOption(), icon: false});\n this.shortcut_key_list.left_column[0].shortcuts[1].keys.unshift({name: Utils.AltOrOption(), icon: false});\n }\n\n // チャンネル ID をセット\n this.channelsStore.channel_id = this.$route.params.channel_id;\n\n // Virtual Keyboard API に対応している場合は、仮想キーボード周りの操作を自力で行うことをブラウザに伝える\n // この視聴画面のみ\n if ('virtualKeyboard' in navigator) {\n navigator.virtualKeyboard.overlaysContent = true;\n // 仮想キーボードが表示されたり閉じられたときのイベント\n navigator.virtualKeyboard.ongeometrychange = (event) => {\n if (event.target.boundingRect.width === 0 && event.target.boundingRect.height === 0) {\n this.is_virtual_keyboard_display = false;\n } else {\n this.is_virtual_keyboard_display = true;\n }\n };\n }\n\n // 再生セッションを初期化\n this.init();\n\n // RomSound を鳴らすための AudioContext を生成\n this.romsounds_context = new AudioContext();\n\n // 01 ~ 14 まですべての RomSound を読み込む\n for (let index = 1; index <= 14; index++) {\n\n // ArrayBuffer として RomSound を取得\n const url = `/assets/romsounds/${index.toString().padStart(2, '0')}.wav`;\n const audio_data = await APIClient.get(url, {\n baseURL: '', // BaseURL を明示的にクライアントのルートに設定\n responseType: 'arraybuffer',\n });\n\n // ArrayBuffer をデコードして AudioBuffer にし、すぐ呼び出せるように貯めておく\n // ref: https://ics.media/entry/200427/\n this.romsounds_buffers.push(await this.romsounds_context.decodeAudioData(audio_data.data));\n }\n },\n // 終了前に実行\n beforeDestroy() {\n\n // 仮想キーボード周りの操作をブラウザに戻す\n if ('virtualKeyboard' in navigator) {\n navigator.virtualKeyboard.overlaysContent = false;\n }\n\n // destroy() を実行\n // 別のページへ遷移するため、DPlayer のインスタンスを確実に破棄する\n // さもなければ、ブラウザがリロードされるまでバックグラウンドで永遠に再生され続けてしまう\n // 不正な ID のため 404 ページに遷移されるときは実行しない\n if (this.channelsStore.channel.current.channel_id !== 'gr999') {\n this.destroy(true);\n }\n\n // AudioContext のリソースを解放\n if (this.romsounds_context !== null) {\n this.romsounds_context.close();\n }\n\n // このページから離れるので、チャンネル ID を gr000 (ダミー値) に戻す\n this.channelsStore.channel_id = 'gr000';\n },\n // チャンネル切り替え時に実行\n // コンポーネント(インスタンス)は再利用される\n // ref: https://v3.router.vuejs.org/ja/guide/advanced/navigation-guards.html#%E3%83%AB%E3%83%BC%E3%83%88%E5%8D%98%E4%BD%8D%E3%82%AB%E3%82%99%E3%83%BC%E3%83%88%E3%82%99\n beforeRouteUpdate(to, from, next) {\n\n // 前の再生セッションを破棄して終了する\n const destroy_promise = this.destroy(false, this.is_zapping_continuously);\n\n // 連続してチャンネルを切り替えていることを示すフラグを立てる\n // このフラグは再生セッションが初期化されるタイミングで必ず降ろされる\n this.is_zapping_continuously = true;\n\n // チャンネル ID を次のチャンネルのものに切り替える\n this.channelsStore.channel_id = to.params.channel_id;\n\n // ハッシュタグフォームのリセットがオンなら、ハッシュタグフォームを空にする\n if (this.settingsStore.settings.reset_hashtag_when_program_switches === true) {\n (this.$refs.Twitter as InstanceType).tweet_hashtag = '';\n }\n\n (async () => {\n\n // ザッピング(「前/次のチャンネル」ボタン or 上下キーショートカット)によるチャンネル移動時のみ、\n // 0.5秒だけ待ってから新しい再生セッションを初期化する\n // 連続してチャンネルを切り替えた際に毎回再生処理を開始しないように猶予を設ける\n if (this.is_zapping === true) {\n this.is_zapping = false;\n this.interval_ids.push(window.setTimeout(() => {\n this.is_zapping_continuously = false; // 新しいセッションを初期化するので、フラグを下ろす\n destroy_promise.then(() => this.init()); // destroy() の実行完了を待ってから初期化する\n }, 0.5 * 1000));\n\n // 通常のチャンネル移動時は、すぐに再生セッションを初期化する\n } else {\n this.is_zapping_continuously = false; // 新しいセッションを初期化するので、フラグを下ろす\n destroy_promise.then(() => this.init()); // destroy() の実行完了を待ってから初期化する\n }\n })();\n\n // 次のルートに置き換え\n next();\n },\n watch: {\n\n // 視聴中のチャンネルが変更されたときのイベント\n 'channelsStore.channel': {\n immediate: true,\n handler(\n new_channel: {previous: IChannel; current: IChannel; next: IChannel;},\n old_channel: {previous: IChannel; current: IChannel; next: IChannel;} | undefined,\n ) {\n\n // old_channel が undefined の場合は、初回のイベント発火なので何もしない\n if (old_channel === undefined) {\n return;\n }\n\n // Twitter コンポーネントのインスタンスを取得\n const twitter_component = this.$refs.Twitter as InstanceType;\n\n // 前のチャンネル情報と次のチャンネル情報で channel_id が変わってたら局タグ追加処理を走らせる\n if (new_channel.current.channel_id !== old_channel.current.channel_id) {\n const old_channel_hashtag = twitter_component.getChannelHashtag(old_channel.current.channel_name) ?? '';\n twitter_component.tweet_hashtag =\n twitter_component.formatHashtag(twitter_component.tweet_hashtag.replaceAll(old_channel_hashtag, ''));\n twitter_component.updateTweetLetterCount();\n }\n\n // 取得したチャンネル情報と現在のチャンネル情報の NID-SID-EID の組み合わせが異なる場合\n if ((new_channel.current.id !== old_channel.current.id) || // チャンネルが異なる\n (new_channel.current.program_present !== null && old_channel.current.program_present === null) || // 番組情報あり→番組情報なし\n (new_channel.current.program_present === null && old_channel.current.program_present !== null) || // 番組情報なし→番組情報あり\n ((new_channel.current.program_present !== null && old_channel.current.program_present !== null) &&\n (new_channel.current.program_present.id !== old_channel.current.program_present.id))) { // 番組情報あり→番組情報あり & 番組が異なる\n\n // ハッシュタグフォームのリセットがオンなら、ハッシュタグフォームを空にする\n if (this.settingsStore.settings.reset_hashtag_when_program_switches === true) {\n twitter_component.tweet_hashtag = twitter_component.formatHashtag('');\n twitter_component.updateTweetLetterCount();\n }\n }\n },\n },\n\n // 前回視聴画面を開いた際にパネルが表示されていたかどうかを保存\n is_panel_display() {\n this.settingsStore.settings.showed_panel_last_time = this.is_panel_display;\n }\n },\n methods: {\n\n // 再生セッションを初期化する\n async init() {\n\n // ローディング中の背景画像をランダムで設定\n this.background_url = PlayerUtils.generatePlayerBackgroundURL();\n\n // コントロール表示タイマーを実行\n this.controlDisplayTimer();\n\n // 現在時刻を1秒おきに更新\n this.interval_ids.push(window.setInterval(() => this.time = dayjs().format('YYYY/MM/DD HH:mm:ss'), 1 * 1000));\n\n // 00秒までの残り秒数を取得\n // 現在 16:01:34 なら 26 (秒) になる\n const residue_second = 60 - new Date().getSeconds();\n\n // 00秒になるまで待ってから実行するタイマー\n // 番組は基本1分単位で組まれているため、20秒や45秒など中途半端な秒数で更新してしまうと番組情報の反映が遅れてしまう\n this.interval_ids.push(window.setTimeout(() => {\n\n // この時点で00秒なので、チャンネル情報を更新\n this.channelsStore.update(true);\n this.update();\n\n // 以降、30秒おきにチャンネル情報を更新\n this.interval_ids.push(window.setInterval(() => {\n this.channelsStore.update(true);\n this.update();\n }, 30 * 1000));\n\n }, residue_second * 1000));\n\n // チャンネル情報を更新 (初回)\n await this.channelsStore.update();\n this.update();\n },\n\n // 画面を更新する\n async update() {\n\n // チャンネル ID が未定義なら実行しない(フェイルセーフ)\n if (this.$route.params.channel_id === undefined) {\n return;\n }\n\n // もし現時点でチャンネル ID が gr999 だった場合、チャンネル情報に存在しない不正な ID なので、404 ページにリダイレクト\n if (this.channelsStore.channel.current.channel_id === 'gr999') {\n this.$router.push({path: '/not-found/'});\n return;\n }\n\n // プレイヤーがまだ初期化されていない or 他のチャンネルからの切り替えですでにプレイヤーが初期化されているけど破棄が可能\n // update() 自体は初期化時以外にも1分ごとに定期実行されるため、その際に毎回プレイヤーを再初期化しないようにする\n if (this.player === null || this.player_can_be_destroyed === true) {\n\n // プレイヤー (DPlayer) 周りのセットアップ\n this.initPlayer();\n\n // サーバーから送られてくるメッセージのイベントハンドラーを初期化\n this.initEventHandler();\n\n // キャプチャのイベントハンドラーを初期化\n this.initCaptureHandler();\n\n // ショートカットキーのイベントハンドラーを初期化\n // 事前に前のイベントハンドラーを削除しておかないと、重複してキー操作が実行されてしまう\n // 直前で実行しないと上下キーでのチャンネル操作が動かなくなる\n document.removeEventListener('keydown', this.shortcut_key_handler);\n this.initShortcutKeyHandler();\n }\n\n if (this.player === null) {\n return; // 復旧不可能 (発生しないはずだが、書いとかないと TypeScript に怒られる)\n }\n\n // 副音声がない番組でプレイヤー上で副音声に切り替えられないように\n // 音声多重放送でもデュアルモノでもない番組のみ\n if ((this.channelsStore.channel.current.program_present === null) ||\n ((this.channelsStore.channel.current.program_present.primary_audio_type !== '1/0+1/0モード(デュアルモノ)') &&\n (this.channelsStore.channel.current.program_present.secondary_audio_type === null))) {\n\n // クラスを付与\n this.player.template.audioItem[1].classList.add('dplayer-setting-audio-item--disabled');\n\n // 現在副音声が選択されている可能性を考慮し、明示的に主音声に切り替える\n if (this.player.plugins.mpegts !== undefined || this.player.plugins.liveLLHLSForKonomiTV !== undefined) {\n // プレイヤーの初期化が完了するまで少し待つ\n while (this.player === null) {\n await Utils.sleep(0.1);\n }\n this.player.template.audioItem[0].classList.add('dplayer-setting-audio-current');\n this.player.template.audioItem[1].classList.remove('dplayer-setting-audio-current');\n this.player.template.audioValue.textContent = this.player.tran('Primary audio');\n try {\n if (this.player.plugins.mpegts !== undefined && this.player.plugins.mpegts instanceof mpegts.MSEPlayer) {\n this.player.plugins.mpegts.switchPrimaryAudio();\n }\n if (this.player.plugins.liveLLHLSForKonomiTV !== undefined) {\n this.player.plugins.liveLLHLSForKonomiTV.switchPrimaryAudio();\n }\n } catch (error) {\n // pass\n }\n }\n\n // 音声多重放送かデュアルモノなので、副音声への切り替えを有効化\n } else {\n this.player.template.audioItem[1].classList.remove('dplayer-setting-audio-item--disabled');\n }\n\n // MediaSession API を使い、メディア通知の表示をカスタマイズ\n if ('mediaSession' in navigator) {\n\n // アートワークとして表示するアイコン\n const artwork = [\n {src: '/assets/images/icons/icon-maskable-192px.png', sizes: '192x192', type: 'image/png'},\n {src: '/assets/images/icons/icon-maskable-512px.png', sizes: '512x512', type: 'image/png'},\n ];\n\n // メディア通知の表示をカスタマイズ\n navigator.mediaSession.metadata = new MediaMetadata({\n title: this.channelsStore.channel.current.program_present?.title ?? '放送休止',\n artist: this.channelsStore.channel.current.channel_name,\n artwork: artwork,\n });\n\n // 再生状況のステータスを設定\n if ('setPositionState' in navigator.mediaSession) {\n navigator.mediaSession.setPositionState({\n duration: 0, // ライブなので0(長さなしを表すらしい)に設定\n playbackRate: 1, // ライブなので再生速度は常に1になる\n });\n }\n\n // 一旦既存のイベントハンドラーを削除\n navigator.mediaSession.setActionHandler('play', null);\n navigator.mediaSession.setActionHandler('pause', null);\n navigator.mediaSession.setActionHandler('previoustrack', null);\n navigator.mediaSession.setActionHandler('nexttrack', null);\n\n // メディア通知上のボタンが押されたときのイベント\n navigator.mediaSession.setActionHandler('play', () => this.player?.play()); // 再生\n navigator.mediaSession.setActionHandler('pause', () => this.player?.pause()); // 停止\n navigator.mediaSession.setActionHandler('previoustrack', async () => { // 前のチャンネルに切り替え\n navigator.mediaSession.metadata = new MediaMetadata({\n title: this.channelsStore.channel.previous.program_present?.title ?? '放送休止',\n artist: this.channelsStore.channel.previous.channel_name,\n artwork: artwork,\n });\n // ルーティングを前のチャンネルに置き換える\n await this.$router.push({path: `/tv/watch/${this.channelsStore.channel.previous.channel_id}`});\n });\n navigator.mediaSession.setActionHandler('nexttrack', async () => { // 次のチャンネルに切り替え\n navigator.mediaSession.metadata = new MediaMetadata({\n title: this.channelsStore.channel.next.program_present?.title ?? '放送休止',\n artist: this.channelsStore.channel.next.channel_name,\n artwork: artwork,\n });\n // ルーティングを次のチャンネルに置き換える\n await this.$router.push({path: `/tv/watch/${this.channelsStore.channel.next.channel_id}`});\n });\n }\n },\n\n // マウスが動いたりタップされた時のイベント\n // 3秒間何も操作がなければコントロールを非表示にする\n controlDisplayTimer(event: Event | null = null, is_player_event: boolean = false) {\n\n // タッチデバイスかどうか\n // DPlayer の UA 判定コードと同一\n const is_touch_device = /iPhone|iPad|iPod|Windows|Macintosh|Android|Mobile/i.test(navigator.userAgent) && 'ontouchend' in document;\n\n // タッチデバイスで mousemove 、あるいはタッチデバイス以外で touchmove か click が発火した時は実行じない\n if (is_touch_device == true && event !== null && event.type === 'mousemove') return;\n if (is_touch_device == false && event !== null && (event.type === 'touchmove' || event.type === 'click')) return;\n\n // 以前セットされたタイマーを止める\n window.clearTimeout(this.control_interval_id);\n\n // setTimeout に渡すタイマー関数\n const timeout = () => {\n\n // コメント入力フォームが表示されているときは実行しない\n // タイマーを掛け直してから抜ける\n if (this.player !== null && this.player.template.controller.classList.contains('dplayer-controller-comment')) {\n this.control_interval_id = window.setTimeout(timeout, 3 * 1000);\n return;\n }\n\n // コントロールを非表示にする\n this.is_control_display = false;\n\n // プレイヤーのコントロールと設定パネルを非表示にする\n if (this.player !== null) {\n this.player.controller.hide();\n this.player.setting.hide();\n }\n };\n\n // タッチデバイスでプレイヤー画面がクリックされたとき\n if (is_touch_device === true && is_player_event === true) {\n\n // プレイヤーのコントロールの表示状態に合わせる\n if (this.player?.controller.isShow()) {\n\n // コントロールを表示する\n this.is_control_display = true;\n\n // プレイヤーのコントロールを表示する\n this.player.controller.show();\n\n // 3秒間何も操作がなければコントロールを非表示にする\n // 3秒間の間一度でもマウスが動けばタイマーが解除されてやり直しになる\n this.control_interval_id = window.setTimeout(timeout, 3 * 1000);\n\n } else {\n\n // コントロールを非表示にする\n this.is_control_display = false;\n\n // プレイヤーのコントロールと設定パネルを非表示にする\n this.player?.controller.hide();\n this.player?.setting.hide();\n }\n\n // それ以外の画面がクリックされたとき\n } else {\n\n // コントロールを表示する\n this.is_control_display = true;\n\n // プレイヤーのコントロールを表示する\n if (this.player !== null) {\n this.player.controller.show();\n }\n\n // 3秒間何も操作がなければコントロールを非表示にする\n // 3秒間の間一度でもマウスが動けばタイマーが解除されてやり直しになる\n this.control_interval_id = window.setTimeout(timeout, 3 * 1000);\n }\n },\n\n // プレイヤーを初期化する\n initPlayer() {\n\n // mpegts.js を window 直下に入れる\n // こうしないと DPlayer が mpegts.js を認識できない\n (window as any).mpegts = mpegts;\n\n // すでに DPlayer が初期化されている場合は破棄する\n // チャンネル切り替え時などが該当する\n if (this.player !== null && this.player_can_be_destroyed === true) {\n try {\n this.player.destroy();\n } catch (error) {\n // mpegts.js をうまく破棄できない場合\n if (this.player.plugins.mpegts !== undefined) {\n this.player.plugins.mpegts.destroy();\n }\n }\n this.player_can_be_destroyed = false;\n this.player = null;\n }\n\n // 低遅延モードであれば低遅延向けの再生バッファを、そうでなければ通常の再生バッファをセット (秒単位)\n const playback_buffer_sec = this.settingsStore.settings.tv_low_latency_mode ?\n PLAYBACK_BUFFER_SEC_LOW_LATENCY : PLAYBACK_BUFFER_SEC;\n\n // DPlayer を初期化\n this.player = new DPlayer({\n container: this.$el.querySelector('.watch-player__dplayer')!,\n theme: '#E64F97', // テーマカラー\n lang: 'ja-jp', // 言語\n live: true, // ライブモード\n liveSyncMinBufferSize: this.is_mpegts_supported ? playback_buffer_sec - 0.1 : 0, // ライブモードで同期する際の最小バッファサイズ\n loop: false, // ループ再生 (ライブのため無効化)\n airplay: false, // AirPlay 機能 (うまく動かないため無効化)\n autoplay: true, // 自動再生\n hotkey: false, // ショートカットキー(こちらで制御するため無効化)\n screenshot: false, // スクリーンショット (こちらで制御するため無効化)\n volume: 1.0, // 音量の初期値\n // 映像\n video: {\n // デフォルトの品質\n // ラジオチャンネルでは常に 48KHz/192kbps に固定する\n defaultQuality: (this.channelsStore.channel.current.is_radiochannel) ?\n '48kHz/192kbps' : this.settingsStore.settings.tv_streaming_quality,\n // 品質リスト\n quality: (() => {\n const qualities: DPlayerType.VideoQuality[] = [];\n\n // ラジオチャンネル\n // API が受け付ける品質の値は通常のチャンネルと同じだが (手抜き…)、実際の品質は 48KHz/192kbps で固定される\n // ラジオチャンネルの場合は、1080p と渡しても 48kHz/192kbps 固定の音声だけの MPEG-TS が配信される\n if (this.channelsStore.channel.current.is_radiochannel) {\n // mpegts.js\n if (this.is_mpegts_supported === true) {\n qualities.push({\n name: '48kHz/192kbps',\n type: 'mpegts',\n url: `${Utils.api_base_url}/streams/live/${this.channelsStore.channel_id}/1080p/mpegts`,\n });\n // LL-HLS (mpegts.js がサポートされていない場合)\n } else {\n qualities.push({\n name: '48kHz/192kbps',\n type: 'live-llhls-for-KonomiTV',\n url: `${Utils.api_base_url}/streams/live/${this.channelsStore.channel_id}/1080p/ll-hls`,\n });\n }\n\n // 通常のチャンネル\n } else {\n\n // ブラウザが H.265 / HEVC の再生に対応していて、かつ通信節約モードが有効なとき\n // API に渡す画質に -hevc のプレフィックスをつける\n let hevc_prefix = '';\n if (PlayerUtils.isHEVCVideoSupported() && this.settingsStore.settings.tv_data_saver_mode === true) {\n hevc_prefix = '-hevc';\n }\n\n // 品質リストを作成\n for (const quality of ['1080p-60fps', '1080p', '810p', '720p', '540p', '480p', '360p', '240p']) {\n // mpegts.js\n if (this.is_mpegts_supported === true) {\n qualities.push({\n name: quality === '1080p-60fps' ? '1080p (60fps)' : quality,\n type: 'mpegts',\n url: `${Utils.api_base_url}/streams/live/${this.channelsStore.channel_id}/${quality}${hevc_prefix}/mpegts`,\n });\n // LL-HLS (mpegts.js がサポートされていない場合)\n } else {\n qualities.push({\n name: quality === '1080p-60fps' ? '1080p (60fps)' : quality,\n type: 'live-llhls-for-KonomiTV',\n url: `${Utils.api_base_url}/streams/live/${this.channelsStore.channel_id}/${quality}${hevc_prefix}/ll-hls`,\n });\n }\n }\n }\n return qualities;\n })(),\n },\n // コメント\n danmaku: {\n user: 'KonomiTV', // 便宜上 KonomiTV に固定\n speedRate: this.settingsStore.settings.comment_speed_rate, // コメントの流れる速度\n fontSize: this.settingsStore.settings.comment_font_size, // コメントのフォントサイズ\n },\n // コメント API バックエンド\n apiBackend: {\n // コメント取得時\n read: (options) => {\n // 空の配列を返す (こうするとコメント0件と認識される)\n options.success([]);\n },\n // コメント送信時\n send: async (options) => {\n // Comment コンポーネント内のコメント送信メソッドを呼び出す\n // ref: https://stackoverflow.com/a/65729556/17124142 ($refs への型設定)\n (this.$refs.Comment as InstanceType).sendComment(options);\n },\n },\n // プラグイン\n pluginOptions: {\n // mpegts.js\n mpegts: {\n config: {\n // Web Worker を有効にする\n enableWorker: true,\n // IO 層のバッファを禁止する\n enableStashBuffer: false,\n // HTMLMediaElement の内部バッファによるライブストリームの遅延を追跡する\n // liveBufferLatencyChasing と異なり、いきなり再生時間をスキップするのではなく、\n // 再生速度を少しだけ上げることで再生を途切れさせることなく遅延を追跡する\n liveSync: this.settingsStore.settings.tv_low_latency_mode,\n // 許容する HTMLMediaElement の内部バッファの最大値 (秒単位, 3秒)\n liveSyncMaxLatency: 3,\n // HTMLMediaElement の内部バッファ (遅延) が liveSyncMaxLatency を超えたとき、ターゲットとする遅延時間 (秒単位)\n liveSyncTargetLatency: playback_buffer_sec,\n // ライブストリームの遅延の追跡に利用する再生速度 (x1.1)\n // 遅延が 3 秒を超えたとき、遅延が playback_buffer_sec を下回るまで再生速度が x1.1 に設定される\n liveSyncPlaybackRate: 1.1,\n }\n },\n // aribb24.js\n aribb24: {\n // 描画フォント\n normalFont: `\"${this.settingsStore.settings.caption_font}\", \"Rounded M+ 1m for ARIB\", sans-serif`,\n // 縁取りする色\n forceStrokeColor: this.settingsStore.settings.always_border_caption_text,\n // 背景色\n forceBackgroundColor: (() => {\n if (this.settingsStore.settings.specify_caption_opacity === true) {\n const opacity = this.settingsStore.settings.caption_opacity;\n return `rgba(0, 0, 0, ${opacity})`;\n } else {\n return null;\n }\n })(),\n // DRCS 文字を対応する Unicode 文字に置換\n drcsReplacement: true,\n // 高解像度の字幕 Canvas を取得できるように\n enableRawCanvas: true,\n // 縁取りに strokeText API を利用\n useStroke: true,\n // Unicode 領域の代わりに私用面の領域を利用 (Windows TV 系フォントのみ)\n usePUA: (() => {\n const font = this.settingsStore.settings.caption_font;\n const context = document.createElement('canvas').getContext('2d')!;\n context.font = '10px \"Rounded M+ 1m for ARIB\"';\n context.fillText('Test', 0, 0);\n context.font = `10px \"${font}\"`;\n context.fillText('Test', 0, 0);\n if (font.startsWith('Windows TV')) {\n return true;\n } else {\n return false;\n }\n })(),\n // 文字スーパーの PRA (内蔵音再生コマンド) のコールバックを指定\n PRACallback: async (index: number) => {\n\n // 設定で文字スーパーが無効なら実行しない\n if (this.settingsStore.settings.tv_show_superimpose === false) return;\n\n // index に応じた内蔵音を鳴らす\n // ref: https://ics.media/entry/200427/\n // ref: https://www.ipentec.com/document/javascript-web-audio-api-change-volume\n\n // 自動再生ポリシーに引っかかったなどで AudioContext が一時停止されている場合、一度 resume() する必要がある\n // resume() するまでに何らかのユーザーのジェスチャーが行われているはず…\n // なくても動くこともあるみたいだけど、念のため\n if (this.romsounds_context.state === 'suspended') {\n await this.romsounds_context.resume();\n }\n\n // index で指定された音声データを読み込み\n const buffer_source_node = this.romsounds_context.createBufferSource();\n buffer_source_node.buffer = this.romsounds_buffers[index];\n\n // GainNode につなげる\n const gain_node = this.romsounds_context.createGain();\n buffer_source_node.connect(gain_node);\n\n // 出力につなげる\n gain_node.connect(this.romsounds_context.destination);\n\n // 音量を元の wav の3倍にする (1倍だと結構小さめ)\n gain_node.gain.value = 3;\n\n // 再生開始\n buffer_source_node.start(0);\n },\n }\n },\n // 字幕\n subtitle: {\n type: 'aribb24', // aribb24.js を有効化\n }\n });\n\n // デバッグ用にプレイヤーインスタンスも window 直下に入れる\n (window as any).player = this.player;\n\n // プレイヤー側のコントロール非表示タイマーを無効化(上書き)\n // 無効化しておかないと、controlDisplayTimer() と競合してしまう\n // 上書き元のコードは https://github.com/tsukumijima/DPlayer/blob/master/src/js/controller.js#L387-L395 にある\n this.player.controller.setAutoHide = (time: number) => {};\n\n // ニコニコ実況セッションを初期化し、随時コメントを受信できるようにする\n // 初期化以降の処理はすべて LiveCommentManager に任せる\n (this.$refs.Comment as InstanceType).initSession(this.player, this.channelsStore.channel_id);\n\n // ***** コメント送信時のイベントハンドラー *****\n\n // コメントが送信されたときに、プレイヤーからのコメント送信から間もないかどうかのフラグを立てる (0.1秒後に解除する)\n // コメントを送信するとコメント入力フォームへのフォーカスが外れるため、ページ全体の keydown イベントでは\n // Enter キーの押下がコメント送信由来のイベントかキャプチャ拡大表示由来のイベントかを判断できない\n // そこで、コメント入力フォームフォーカス中に Enter キーが押された場合(=コメント送信時)に 0.1 秒間フラグを立てることで、\n // ショートカットキーハンドラーがコメント送信由来のイベントであることを判定できるようにしている\n this.player.template.commentInput.addEventListener('keydown', (event) => {\n if (event.code === 'Enter') {\n this.is_comment_send_just_did = true;\n setTimeout(() => this.is_comment_send_just_did = false, 100);\n }\n });\n\n // 「コメント送信後にコメント入力フォームを閉じる」がオフになっている時のために、プレイヤー側のコメント送信関数を上書き\n // 上書き部分以外の処理内容は概ね https://github.com/tsukumijima/DPlayer/blob/master/src/js/comment.js に準じる\n this.player.comment!.send = () => {\n\n if (this.player === null) {\n return; // 復旧不可能 (発生しないはずだが、書いとかないと TypeScript に怒られる)\n }\n\n // コメント入力フォームへのフォーカスを外す (「コメント送信後にコメント入力フォームを閉じる」がオンのときだけ)\n if (this.settingsStore.settings.close_comment_form_after_sending === true) {\n this.player.template.commentInput.blur();\n }\n\n // 空コメントを弾く\n if (!this.player.template.commentInput.value.replace(/^\\s+|\\s+$/g, '')) {\n this.player.notice(this.player.tran('Please input danmaku content!'));\n return;\n }\n\n // コメントを送信\n this.player.danmaku!.send(\n {\n text: this.player.template.commentInput.value,\n color: this.player.container.\n querySelector('.dplayer-comment-setting-color input:checked')!.value,\n type: this.player.container.\n querySelector('.dplayer-comment-setting-type input:checked')!.value as DPlayerType.DanmakuType,\n size: this.player.container.\n querySelector('.dplayer-comment-setting-size input:checked')!.value as DPlayerType.DanmakuSize,\n },\n // 送信完了後にコメント入力フォームを閉じる ([コメント送信後にコメント入力フォームを閉じる] がオンのときだけ)\n () => {\n if (this.settingsStore.settings.close_comment_form_after_sending === true) {\n this.player !== null && this.player.comment!.hide();\n }\n },\n true,\n );\n\n // 重複送信を防ぐ\n this.player.template.commentInput.value = '';\n };\n\n // ***** 設定パネルのショートカット一覧へのリンクのイベントハンドラー *****\n\n // 設定パネルにショートカット一覧を表示するリンクを動的に追加する\n // タッチデバイスでは実行しない\n const is_touch_device = /iPhone|iPad|iPod|Macintosh|Android|Mobile/i.test(navigator.userAgent) && 'ontouchend' in document;\n if (is_touch_device === false) {\n this.player.template.settingOriginPanel.insertAdjacentHTML('beforeend', `\n
\n キーボードショートカット\n
\n \n \n \n
\n
`);\n\n // 設定パネルの高さを再設定\n const settingOriginPanelHeight = this.player.template.settingOriginPanel.scrollHeight;\n this.player.template.settingBox.style.clipPath = `inset(calc(100% - ${settingOriginPanelHeight}px) 0 0 round 7px)`;\n\n // 設定パネルのショートカット一覧を表示するリンクがクリックされたときのイベント\n // リアクティブではないので、手動でやらないといけない…\n this.$el.querySelector('.dplayer-setting-keyboard-shortcut')!.addEventListener('click', () => {\n this.player?.setting.hide(); // 設定パネルを閉じる\n this.shortcut_key_modal = true;\n });\n }\n\n // ***** フルスクリーンのイベントハンドラー *****\n\n // フルスクリーンにするコンテナ要素(ページ全体)\n const fullscreen_container = document.querySelector('.v-application')!;\n this.fullscreen_handler = () => {\n this.is_fullscreen = this.player?.fullScreen.isFullScreen() === true;\n };\n if (fullscreen_container.onfullscreenchange !== undefined) {\n fullscreen_container.addEventListener('fullscreenchange', this.fullscreen_handler);\n } else {\n fullscreen_container.addEventListener('webkitfullscreenchange', this.fullscreen_handler);\n }\n\n // DPlayer のフルスクリーン関係のメソッドを無理やり上書きし、KonomiTV の UI と統合する\n // 上書き元のコードは https://github.com/tsukumijima/DPlayer/blob/master/src/js/fullscreen.js にある\n // フルスクリーンかどうか\n this.player.fullScreen.isFullScreen = (type?: DPlayerType.FullscreenType) => {\n return !!(document.fullscreenElement || document.webkitFullscreenElement);\n };\n // フルスクリーンをリクエスト\n this.player.fullScreen.request = (type?: DPlayerType.FullscreenType) => {\n if (this.player === null) return;\n\n // すでにフルスクリーンだったらキャンセルする\n if (this.player.fullScreen.isFullScreen()) {\n this.player.fullScreen.cancel();\n return;\n }\n\n // フルスクリーンをリクエスト\n // Safari は webkit のベンダープレフィックスが必要\n fullscreen_container.requestFullscreen = fullscreen_container.requestFullscreen || fullscreen_container.webkitRequestFullscreen;\n if (fullscreen_container.requestFullscreen) {\n fullscreen_container.requestFullscreen();\n } else {\n // フルスクリーンがサポートされていない場合はエラーを表示\n this.player.notice('iPhone Safari は動画のフルスクリーン表示に対応していません。');\n return;\n }\n\n // 画面の向きを横に固定 (Screen Orientation API がサポートされている場合)\n if (screen.orientation) {\n screen.orientation.lock('landscape').catch(() => {});\n }\n };\n // フルスクリーンをキャンセル\n this.player.fullScreen.cancel = (type?: DPlayerType.FullscreenType) => {\n\n // フルスクリーンを終了\n // Safari は webkit のベンダープレフィックスが必要\n document.exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen;\n if (document.exitFullscreen) {\n document.exitFullscreen();\n }\n\n // 画面の向きの固定を解除\n if (screen.orientation) {\n screen.orientation.unlock();\n }\n };\n\n // ***** 再生/停止/画質切り替え時のイベントハンドラー *****\n\n // 再生/停止されたとき\n // 通知バーからの制御など、画面から以外の外的要因で再生/停止が行われる事もある\n const on_play_or_pause = () => {\n\n // まだ設定パネルが表示されていたら非表示にする\n this.player?.setting.hide();\n\n // コントロールを表示する\n this.controlDisplayTimer();\n };\n this.player.on('play', on_play_or_pause);\n this.player.on('pause', on_play_or_pause);\n\n // 画質の切り替えが開始されたときのイベント\n this.player.on('quality_start', () => {\n\n // ローディング中の背景画像をランダムで設定\n this.background_url = PlayerUtils.generatePlayerBackgroundURL();\n\n // イベントソースを閉じる\n if (this.eventsource !== null) {\n this.eventsource.close();\n this.eventsource = null;\n }\n\n // 新しい EventSource を作成\n // 画質ごとにイベント API は異なるため、一度破棄してから作り直す\n this.initEventHandler();\n });\n\n // 停止状態でかつ再生時間からバッファが 30 秒以上離れていないかを監視し、そうなっていたら強制的にシークする\n // mpegts.js の仕様上、MSE に未再生のバッファがたまり過ぎると SourceBuffer が追加できなくなるため、強制的に接続が切断されてしまう\n // LL-HLS 再生時も、ずっと停止したままだとプレイリストやセグメントに HTTP リクエストされなくなり、サーバー側でタイムアウトさせられてしまう\n // mpegts.js 再生時は 60 秒、LL-HLS 再生時は 30 秒おきに監視する (LL-HLS 再生時はバッファの状態に関わらずシークする)\n if (this.is_mpegts_supported === true) {\n this.interval_ids.push(window.setInterval(() => {\n if (this.player === null) return;\n if ((this.player.video.paused && this.player.video.buffered.length >= 1) &&\n (this.player.video.buffered.end(0) - this.player.video.currentTime > 30)) {\n this.player.sync();\n }\n }, 60 * 1000));\n } else {\n this.interval_ids.push(window.setInterval(() => {\n if (this.player === null) return;\n if (this.player.video.paused) {\n this.player.sync();\n }\n }, 30 * 1000));\n }\n\n // ***** 文字スーパーのイベントハンドラー *****\n\n (async () => {\n\n // 文字スーパーが初期化されるまで待つ\n if (this.player === null) return;\n while (this.player.plugins.aribb24Superimpose === undefined) {\n await Utils.sleep(0.1); // 0.1 秒待つ\n }\n\n // 設定で文字スーパーが有効\n // 字幕が非表示の場合でも、文字スーパーは表示する\n if (this.settingsStore.settings.tv_show_superimpose === true) {\n this.player.plugins.aribb24Superimpose.show();\n this.player.on('subtitle_hide', () => {\n this.player?.plugins.aribb24Superimpose!.show();\n });\n // 設定で文字スーパーが無効\n } else {\n this.player.plugins.aribb24Superimpose.hide();\n this.player.on('subtitle_show', () => {\n this.player?.plugins.aribb24Superimpose!.hide();\n });\n }\n\n })();\n },\n\n // イベントハンドラーを初期化する\n initEventHandler() {\n\n // ***** プレイヤー再生開始時のイベントハンドラー *****\n\n if (this.player === null) return;\n\n // 必ず最初はローディング状態とする\n this.is_loading = true;\n\n // 音量を 0 に設定\n this.player.video.volume = 0;\n\n // video 要素の crossOrigin 属性を 'anonymous' に設定\n // これを設定しないと、クロスオリジンの場合にキャプチャができない\n this.player.video.crossOrigin = 'anonymous';\n\n // mpegts.js 再生時のみ、mpegts.js のログハンドラーを設定する\n if (this.is_mpegts_supported === true && this.player.plugins.mpegts !== undefined) {\n this.player.plugins.mpegts.on(mpegts.Events.ERROR, async (error_type: mpegts.ErrorTypes, detail: mpegts.ErrorDetails) => {\n // 再生中にエラーが発生した場合\n // ワークアラウンドとして通知した後にページをリロードする\n // TODO: ロジックを整理してストリーミングを再起動できるようにする\n this.player.notice(`再生中にエラーが発生しました。(Type: ${error_type}) 3秒後にリロードします。`, -1);\n await Utils.sleep(3);\n location.reload();\n });\n // LL-HLS 再生時は、error イベントを監視してエラーが発生したらページをリロードする\n } else if (this.is_mpegts_supported === false) {\n this.player.on('error', async () => {\n this.player.notice(`再生中にエラーが発生しました。(Type: ${this.player.video.error.message}) 3秒後にリロードします。`, -1);\n await Utils.sleep(3);\n location.reload();\n });\n }\n\n // LL-HLS 再生時のみ、ローディングが終わるまでは表示上再生状態を維持する\n // play() が正常に実行できればいいのだが、Safari の自動再生制限により失敗することがあるので、\n // その際はアイコンの HTML を書き換えたりして強制的に再生状態にする (苦肉の策)\n if (this.is_mpegts_supported === false) {\n const force_play = () => {\n this.player?.video.play().catch(() => {\n console.warn('HTMLVideoElement.play() rejected. run fallback.');\n const pause_icon = '';\n this.player!.template.playButton.innerHTML = pause_icon;\n this.player!.template.mobilePlayButton.innerHTML = pause_icon;\n this.player!.container.classList.remove('dplayer-paused');\n this.player!.container.classList.add('dplayer-playing');\n this.player!.danmaku!.play();\n });\n // ローディング表示が消えたタイミングでイベントを登録解除\n if (this.is_loading === false) {\n this.player?.video.removeEventListener('pause', force_play);\n return;\n }\n };\n this.player.video.addEventListener('pause', force_play);\n }\n\n // 再生バッファを調整し、再生準備ができた段階でプレイヤーの背景を非表示にするイベントを登録\n // 実際に再生可能になるのを待ってから実行する\n // 画質切り替え時にも実行する必要があるので、あえてこの位置に記述している\n const on_canplay = async () => {\n\n // 自分自身のイベントを登録解除 (重複実行を避ける)\n if (this.player === null) return;\n this.player.video.oncanplay = null;\n this.player.video.oncanplaythrough = null;\n\n // mpegts.js 利用時のみ実行\n if (this.is_mpegts_supported === true) {\n\n // 再生バッファ調整のため、一旦停止させる\n // this.player.video.pause() を使うとプレイヤーの UI アイコンが停止してしまうので、代わりに playbackRate を使う\n this.player.video.playbackRate = 0;\n\n // 再生バッファを取得する (取得に失敗した場合は 0 を返す)\n const get_playback_buffer_sec = (): number => {\n let buffered_end = 0;\n if (this.player.video.buffered.length >= 1) {\n buffered_end = this.player.video.buffered.end(0);\n }\n return (Math.round((buffered_end - this.player.video.currentTime) * 1000) / 1000);\n };\n\n // 低遅延モードであれば低遅延向けの再生バッファを、そうでなければ通常の再生バッファをセット (秒単位)\n const playback_buffer_sec = this.settingsStore.settings.tv_low_latency_mode ?\n PLAYBACK_BUFFER_SEC_LOW_LATENCY : PLAYBACK_BUFFER_SEC;\n\n // 再生バッファが playback_buffer_sec を超えるまで 0.1 秒おきに再生バッファをチェックする\n // 再生バッファが playback_buffer_sec を切ると再生が途切れやすくなるので (特に動きの激しい映像)、\n // 再生開始までの時間を若干犠牲にして、再生バッファの調整と同期に時間を割く\n // playback_buffer_sec の値は mpegts.js に渡す liveSyncTargetLatency プロパティに渡す値と共通\n let current_playback_buffer_sec = get_playback_buffer_sec();\n while (current_playback_buffer_sec < playback_buffer_sec) {\n await Utils.sleep(0.1);\n current_playback_buffer_sec = get_playback_buffer_sec();\n }\n\n // 再生開始\n this.player.video.playbackRate = 1;\n }\n\n const recover = async () => {\n await Utils.sleep(0.5);\n // この時点で映像が停止している場合、復旧を試みる\n if (this.player?.video.readyState !== 4) {\n console.log('player.video.readyState !== ENOUGH_DATA. trying to recover.');\n this.player?.video.pause();\n await Utils.sleep(0.1);\n this.player?.video.play().catch(() => {\n console.warn('HTMLVideoElement.play() rejected. paused.');\n this.player?.pause();\n });\n }\n };\n\n // 再生が一時的に止まってバッファリングしているとき/再び再生されはじめたときのイベント\n // バッファリングの Progress Circular の表示を制御する\n // 同期が終わってからの方が都合が良い\n this.player.video.addEventListener('waiting', () => this.is_video_buffering = true);\n this.player.video.addEventListener('playing', () => {\n this.is_video_buffering = false;\n recover();\n });\n\n // ローディング状態を解除し、映像を表示する\n this.is_loading = false;\n\n // バッファリング中の Progress Circular を非表示にする\n this.is_video_buffering = false;\n recover();\n\n if (this.channelsStore.channel.current.is_radiochannel) {\n // ラジオチャンネルでは引き続き映像の代わりとして背景画像を表示し続ける\n this.is_background_display = true;\n } else {\n // 背景画像をフェードアウト\n this.is_background_display = false;\n }\n\n // 再生開始時に音量を徐々に上げる\n // いきなり再生されるよりも体験が良い\n const current_volume = this.player.user.get('volume');\n while ((this.player.video.volume + 0.05) < current_volume) {\n // 小数第2位以下を切り捨てて、浮動小数の誤差で 1 (100%) を微妙に超えてしまいエラーになるのを避ける\n this.player.video.volume = Utils.mathFloor(this.player.video.volume + 0.05, 2);\n await Utils.sleep(0.02);\n }\n this.player.video.volume = current_volume;\n };\n this.player.video.oncanplay = on_canplay;\n this.player.video.oncanplaythrough = on_canplay;\n\n // ***** KonomiTV サーバーのイベント API のイベントハンドラー *****\n\n // EventSource を作成\n const eventsource_url = (this.player!.quality!.url as string).replace('/mpegts', '/events').replace(/\\/ll-hls.*/, '/events');\n this.eventsource = new EventSource(eventsource_url);\n\n // 初回接続時のイベント\n this.eventsource.addEventListener('initial_update', (event_raw: MessageEvent) => {\n\n // イベントを取得\n const event = JSON.parse(event_raw.data);\n console.log(`[initial_update] Status: ${event.status} / Detail: ${event.detail}`);\n\n // ステータスごとに処理を振り分け\n switch (event.status) {\n\n // Status: Standby\n case 'Standby': {\n\n // バッファリング中の Progress Circular を表示\n this.is_video_buffering = true;\n\n // プレイヤーの背景を表示する\n this.is_background_display = true;\n break;\n }\n }\n });\n\n // ステータスが更新されたときのイベント\n this.eventsource.addEventListener('status_update', async (event_raw: MessageEvent) => {\n\n // イベントを取得\n if (this.player === null) return;\n const event = JSON.parse(event_raw.data);\n console.log(`[status_update] Status: ${event.status} / Detail: ${event.detail}`);\n\n // 視聴者数を更新\n this.channelsStore.updateChannel(this.channelsStore.channel_id, {\n ...this.channelsStore.channel.current,\n viewers: event.clients_count,\n });\n\n // ステータスごとに処理を振り分け\n switch (event.status) {\n\n // Status: Standby\n case 'Standby': {\n\n // ステータス詳細をプレイヤーに表示\n if (!this.player.template.notice.textContent!.includes('画質を')) { // 画質切り替えの表示を上書きしない\n this.player.notice(event.detail, -1);\n }\n\n // バッファリング中の Progress Circular を表示\n this.is_video_buffering = true;\n\n // プレイヤーの背景を表示する\n this.is_background_display = true;\n break;\n }\n\n // Status: ONAir\n case 'ONAir': {\n\n // ステータス詳細をプレイヤーから削除\n if (!this.player.template.notice.textContent!.includes('画質を')) { // 画質切り替えの表示を上書きしない\n this.player.notice(this.player.template.notice.textContent!, 0.000001);\n }\n\n // LL-HLS ストリーミング時のみ、このタイミングで映像をロードして再生を開始する\n if (this.is_mpegts_supported === false) {\n this.player.video.load();\n this.player.video.play();\n on_canplay();\n }\n\n // 再生が開始される前にチャンネルを切り替えた際にコメントが流れない不具合のワークアラウンド\n if (this.player.container.classList.contains('dplayer-paused')) {\n this.player.container.classList.remove('dplayer-paused');\n this.player.container.classList.add('dplayer-playing');\n }\n\n // 前のプレイヤーインスタンスの Picture-in-Picture ウインドウが残っている場合、終了させてからもう一度切り替える\n // チャンネル切り替えが完了しても前の Picture-in-Picture ウインドウは再利用されないため、一旦終了させるしかない\n if (document.pictureInPictureElement) {\n document.exitPictureInPicture();\n this.player.video.requestPictureInPicture();\n }\n break;\n }\n\n // Status: Idling\n case 'Idling': {\n\n // 本来誰も視聴していないことを示す Idling ステータスを受信している場合、何らかの理由で\n // ストリーミング API への接続が切断された可能性が高いので、ワークアラウンドとして通知した後にページをリロードする\n // TODO: ロジックを整理してストリーミングを再起動できるようにする\n this.player.notice('ストリーミング接続が切断されました。3秒後にリロードします。', -1);\n await Utils.sleep(3);\n location.reload();\n\n break;\n }\n\n // Status: Restart\n case 'Restart': {\n\n // 「ライブストリームは Offline です。」のステータス詳細を受信すること自体が不正な状態\n // ストリーミング API への接続が切断された可能性が高いので、ワークアラウンドとして通知した後にページをリロードする\n // TODO: ロジックを整理してストリーミングを再起動できるようにする\n if (event.detail === 'ライブストリームは Offline です。') {\n this.player.notice('ストリーミング接続が切断されました。3秒後にリロードします。', -1);\n await Utils.sleep(3);\n location.reload();\n }\n\n // ステータス詳細をプレイヤーに表示\n this.player.notice(event.detail, -1);\n\n // プレイヤーを再起動する\n this.player.switchVideo({\n url: this.player.quality!.url,\n type: this.player.quality!.type,\n });\n\n // 再起動しただけでは自動再生されないので、明示的に\n this.player.play();\n\n // バッファリング中の Progress Circular を表示\n this.is_video_buffering = true;\n\n // プレイヤーの背景を表示する\n this.is_background_display = true;\n break;\n }\n\n // Status: Offline\n // 基本的に Offline は放送休止中やエラーなどで復帰の見込みがない状態\n case 'Offline': {\n\n if (this.player !== null) {\n\n // ステータス詳細をプレイヤーに表示\n // 動画の読み込みエラーが送出された時にメッセージを上書きする\n this.player.notice(event.detail, -1);\n this.player.video.onerror = () => {\n this.player!.notice(event.detail, -1);\n this.player!.video.onerror = null;\n };\n\n // 描画されたコメントをクリア\n this.player?.danmaku?.clear();\n\n // 動画を停止する\n this.player.video.pause();\n }\n\n // イベントソースを閉じる(復帰の見込みがないため)\n if (this.eventsource !== null) {\n this.eventsource.close();\n this.eventsource = null;\n }\n\n // プレイヤーの背景を表示する\n this.is_background_display = true;\n\n // バッファリング中の Progress Circular を非表示にする\n this.is_loading = false;\n this.is_video_buffering = false;\n break;\n }\n }\n });\n\n // ステータス詳細が更新されたときのイベント\n this.eventsource.addEventListener('detail_update', (event_raw: MessageEvent) => {\n\n // イベントを取得\n if (this.player === null) return;\n const event = JSON.parse(event_raw.data);\n console.log(`[detail_update] Status: ${event.status} Detail:${event.detail}`);\n\n // 視聴者数を更新\n this.channelsStore.updateChannel(this.channelsStore.channel_id, {\n ...this.channelsStore.channel.current,\n viewers: event.clients_count,\n });\n\n // ステータスごとに処理を振り分け\n switch (event.status) {\n\n // Status: Standby\n case 'Standby': {\n\n // ステータス詳細をプレイヤーに表示\n this.player.notice(event.detail, -1);\n\n // プレイヤーの背景を表示する\n if (!this.is_background_display) {\n this.is_background_display = true;\n }\n break;\n }\n }\n });\n\n // クライアント数(だけ)が更新されたときのイベント\n this.eventsource.addEventListener('clients_update', (event_raw: MessageEvent) => {\n\n // イベントを取得\n const event = JSON.parse(event_raw.data);\n\n // 視聴者数を更新\n this.channelsStore.updateChannel(this.channelsStore.channel_id, {\n ...this.channelsStore.channel.current,\n viewers: event.clients_count,\n });\n });\n },\n\n // ショートカットキーを初期化する\n initShortcutKeyHandler() {\n\n const twitter_component = (this.$refs.Twitter as InstanceType);\n const tweet_form_element = twitter_component.$el.querySelector('.tweet-form__textarea');\n\n // IME 変換中の状態を保存する\n for (const element of document.querySelectorAll('input[type=text],input[type=search],textarea')) {\n element.addEventListener('compositionstart', () => this.is_ime_composing = true);\n element.addEventListener('compositionend', () => this.is_ime_composing = false);\n }\n\n // ショートカットキーハンドラー\n this.shortcut_key_handler = async (event: KeyboardEvent) => {\n\n const tag = document.activeElement.tagName.toUpperCase();\n const editable = document.activeElement.getAttribute('contenteditable');\n\n // 矢印キーのデフォルトの挙動(スクロール)を抑制\n // キーリピート周りで間引かれるイベントでも event.preventDefault() しないとスクロールしてしまうため、\n // 一番最初のタイミングでやっておく\n // input・textarea・contenteditable 状態の要素では実行しない\n if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.code) &&\n (tag !== 'INPUT' && tag !== 'TEXTAREA' && editable !== '' && editable !== 'true')) {\n event.preventDefault();\n }\n\n // キーリピート(押しっぱなし)状態の場合は基本実行しない\n // 押し続けると何度も同じ動作が実行されて大変な事になる…\n // ただ、キーリピートを使いたい場合もあるので、リピート状態をフラグとして保存する\n let is_repeat = false;\n if (event.repeat) is_repeat = true;\n\n // キーリピート状態は event.repeat を見る事でだいたい検知できるが、最初の何回かは検知できないこともある\n // そこで、0.05 秒以内に連続して発火したキーイベントは間引きも兼ねて実行しない\n const now = Utils.time();\n if (now - this.shortcut_key_pressed_at < 0.05) return;\n this.shortcut_key_pressed_at = now; // 最終押下時刻を更新\n\n // 無名関数の中で実行する\n const result = await (async (): Promise => {\n\n // ***** ツイート入力フォームにフォーカスを当てる/フォーカスを外す *****\n\n // ツイート入力フォームにフォーカスしているときもこのショートカットが動くようにする\n // 以降の if 文で textarea フォーカス時のイベントをすべて弾いてしまっているため、前に持ってきている\n // Tab キーに割り当てている関係で、IME 変換中は実行しない(IME 変換中に実行すると文字変換ができなくなる)\n if (((tag !== 'INPUT' && tag !== 'TEXTAREA' && editable !== '' && editable !== 'true') ||\n (document.activeElement === tweet_form_element)) && this.is_ime_composing === false) {\n if (event.code === 'Tab') {\n\n // ツイート入力フォームにフォーカスがすでに当たっていたら、フォーカスを外して終了\n if (document.activeElement === tweet_form_element) {\n tweet_form_element.blur();\n return true;\n }\n\n // パネルを開く\n this.is_panel_display = true;\n\n // どのタブを開いていたかに関係なく Twitter タブに切り替える\n this.tv_panel_active_tab = 'Twitter';\n\n // ツイート入力フォームの textarea 要素にフォーカスを当てる\n tweet_form_element.focus();\n\n // フォーカスを当てると勝手に横方向にスクロールされてしまうので、元に戻す\n this.$el.scrollLeft = 0;\n\n window.setTimeout(() => {\n\n // 他のタブから切り替えると一発でフォーカスが当たらないことがあるので、ちょっとだけ待ってから念押し\n // $nextTick() だと上手くいかなかった…\n tweet_form_element.focus();\n\n // フォーカスを当てると勝手に横方向にスクロールされてしまうので、元に戻す\n this.$el.scrollLeft = 0;\n\n }, 100); // 0.1秒\n\n return true;\n }\n }\n\n // ***** ツイートを送信する *****\n\n // ツイート入力フォームにフォーカスしているときもこのショートカットが動くようにする\n // Twitter タブ以外を開いているときは実行しない\n // 以降の if 文で textarea フォーカス時のイベントをすべて弾いてしまっているため、前に持ってきている\n if (((tag !== 'INPUT' && tag !== 'TEXTAREA' && editable !== '' && editable !== 'true') ||\n (document.activeElement === tweet_form_element)) &&\n this.tv_panel_active_tab === 'Twitter' &&\n this.is_ime_composing === false) {\n // (Ctrl or Cmd or Shift) + Enter\n // Shift + Enter は隠し機能(間違えたとき用)\n if ((event.ctrlKey || event.metaKey || event.shiftKey) && event.code === 'Enter') {\n twitter_component.$el.querySelector('.tweet-button')!.click();\n return true;\n }\n }\n\n // ***** コメント入力フォームを閉じる *****\n\n // プレイヤーが初期化されていない時・Shift / Alt キーが一緒に押された時に作動しないように\n if (this.player !== null && !event.shiftKey && !event.altKey) {\n\n // コメント入力フォームが表示されているときのみ\n if (this.player.template.controller.classList.contains('dplayer-controller-comment')) {\n // Ctrl or Cmd + M\n if ((event.ctrlKey || event.metaKey) && event.code === 'KeyM') {\n this.player.comment!.hide();\n return true;\n }\n }\n }\n\n // input・textarea・contenteditable 状態の要素でなければ\n // 文字入力中にショートカットキーが作動してしまわないように\n if (tag !== 'INPUT' && tag !== 'TEXTAREA' && editable !== '' && editable !== 'true') {\n\n // キーリピートでない時・Ctrl / Cmd キーが一緒に押された時に作動しないように\n // チャンネル選局のキーボードショートカットを Alt or Option + 数字キー/テンキーに変更する設定が有効なときは、\n // Alt or Option キーが押されていることを条件に追加する\n if (is_repeat === false && !event.ctrlKey && !event.metaKey &&\n (this.settingsStore.settings.tv_channel_selection_requires_alt_key === false || (event.altKey))) {\n\n // ***** 数字キーでチャンネルを切り替える *****\n\n // Shift キーが同時押しされていたら BS チャンネルの方を選局する\n const switch_channel_type = (event.shiftKey) ? 'BS' : 'GR';\n\n // 1~9キー\n let switch_remocon_id: number | null = null;\n if (event.code === 'Digit1' || event.code === 'Digit2' || event.code === 'Digit3' ||\n event.code === 'Digit4' || event.code === 'Digit5' || event.code === 'Digit6' ||\n event.code === 'Digit7' || event.code === 'Digit8' || event.code === 'Digit9') {\n switch_remocon_id = Number(event.code.replace('Digit', ''));\n }\n // 0キー: 10に割り当て\n if (event.code === 'Digit0') switch_remocon_id = 10;\n // -キー: 11に割り当て\n if (event.code === 'Minus') switch_remocon_id = 11;\n // ^キー: 12に割り当て\n if (event.code === 'Equal') switch_remocon_id = 12;\n // 1~9キー (テンキー)\n if (event.code === 'Numpad1' || event.code === 'Numpad2' || event.code === 'Numpad3' ||\n event.code === 'Numpad4' || event.code === 'Numpad5' || event.code === 'Numpad6' ||\n event.code === 'Numpad7' || event.code === 'Numpad8' || event.code === 'Numpad9') {\n switch_remocon_id = Number(event.code.replace('Numpad', ''));\n }\n // 0キー (テンキー): 10に割り当て\n if (event.code === 'Numpad0') switch_remocon_id = 10;\n\n // この時点でリモコン番号が取得できていたら実行\n if (switch_remocon_id !== null) {\n\n // 切り替え先のチャンネルを取得する\n const switch_channel = this.channelsStore.getChannelByRemoconID(switch_channel_type, switch_remocon_id);\n\n // チャンネルが取得できていれば、ルーティングをそのチャンネルに置き換える\n // 押されたキーに対応するリモコン番号のチャンネルがない場合や、現在と同じチャンネル ID の場合は何も起こらない\n if (switch_channel !== null && switch_channel.channel_id !== this.channelsStore.channel_id) {\n await this.$router.push({path: `/tv/watch/${switch_channel.channel_id}`});\n return true;\n }\n }\n }\n\n // キーリピートでない時・Ctrl / Cmd / Shift / Alt キーが一緒に押された時に作動しないように\n if (is_repeat === false && !event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey) {\n\n // ***** キーボードショートカットの一覧を表示する *****\n\n // /(?)キー: キーボードショートカットの一覧を表示する\n if (event.code === 'Slash') {\n this.shortcut_key_modal = !this.shortcut_key_modal;\n return true;\n }\n\n // ***** パネルのタブを切り替える *****\n\n // Pキー: パネルの表示切り替え\n if (event.code === 'KeyP') {\n this.is_panel_display = !this.is_panel_display;\n return true;\n }\n // Kキー: 番組情報タブ\n if (event.code === 'KeyK') {\n this.tv_panel_active_tab = 'Program';\n return true;\n }\n // Lキー: チャンネルタブ\n if (event.code === 'KeyL') {\n this.tv_panel_active_tab = 'Channel';\n return true;\n }\n // ;(+)キー: コメントタブ\n if (event.code === 'Semicolon') {\n this.tv_panel_active_tab = 'Comment';\n return true;\n }\n // :(*)キー: Twitterタブ\n if (event.code === 'Quote') {\n this.tv_panel_active_tab = 'Twitter';\n return true;\n }\n\n // ***** Twitter タブ内のタブを切り替える *****\n\n // [(「): ツイート検索タブ\n if (event.code === 'BracketRight') {\n twitter_component.twitter_active_tab = 'Search';\n return true;\n }\n // ](」): タイムラインタブ\n if (event.code === 'Backslash') {\n twitter_component.twitter_active_tab = 'Timeline';\n return true;\n }\n // \\(¥)キー: キャプチャタブ\n if (event.code === 'IntlRo') {\n twitter_component.twitter_active_tab = 'Capture';\n return true;\n }\n }\n\n // Twitter タブ内のキャプチャタブが表示されている & Ctrl / Cmd / Shift / Alt のいずれも押されていないときだけ\n // キャプチャタブが表示されている時は、プレイヤー操作側の矢印キー/スペースキーのショートカットは動作しない(キーが重複するため)\n if (this.tv_panel_active_tab === 'Twitter' && twitter_component.twitter_active_tab === 'Capture' &&\n (!event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey)) {\n\n // ***** キャプチャにフォーカスする *****\n\n if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.code)) {\n\n // キャプチャリストに一枚もキャプチャがない\n if (twitter_component.captures.length === 0) return false;\n\n // まだどのキャプチャにもフォーカスされていない場合は、一番新しいキャプチャにフォーカスして終了\n if (twitter_component.captures.some(capture => capture.focused === true) === false) {\n twitter_component.captures[twitter_component.captures.length - 1].focused = true;\n return true;\n }\n\n // 現在フォーカスされているキャプチャのインデックスを取得\n const focused_capture_index = twitter_component.captures.findIndex(capture => capture.focused === true);\n\n // ↑キー: 2つ前のキャプチャにフォーカスする\n // キャプチャリストは2列で並んでいるので、2つ後のキャプチャが現在フォーカスされているキャプチャの直上になる\n if (event.code === 'ArrowUp') {\n // 2つ前のキャプチャがないなら実行しない\n if (focused_capture_index - 2 < 0) return false;\n twitter_component.captures[focused_capture_index - 2].focused = true;\n }\n\n // ↓キー: 2つ後のキャプチャにフォーカスする\n // キャプチャリストは2列で並んでいるので、2つ後のキャプチャが現在フォーカスされているキャプチャの直下になる\n if (event.code === 'ArrowDown') {\n // 2つ後のキャプチャがないなら実行しない\n if (focused_capture_index + 2 > (twitter_component.captures.length - 1)) return false;\n twitter_component.captures[focused_capture_index + 2].focused = true;\n }\n\n // ←キー: 1つ前のキャプチャにフォーカスする\n if (event.code === 'ArrowLeft') {\n // 1つ前のキャプチャがないなら実行しない\n if (focused_capture_index - 1 < 0) return false;\n twitter_component.captures[focused_capture_index - 1].focused = true;\n }\n\n // ←キー: 1つ後のキャプチャにフォーカスする\n if (event.code === 'ArrowRight') {\n // 1つ後のキャプチャがないなら実行しない\n if (focused_capture_index + 1 > (twitter_component.captures.length - 1)) return false;\n twitter_component.captures[focused_capture_index + 1].focused = true;\n }\n\n // 現在フォーカスされているキャプチャのフォーカスを外す\n twitter_component.captures[focused_capture_index].focused = false;\n\n // 拡大表示のモーダルが開かれている場合は、フォーカスしたキャプチャをモーダルにセット\n // こうすることで、QuickLook みたいな挙動になる\n const focused_capture = twitter_component.captures.find(capture => capture.focused === true);\n if (twitter_component.zoom_capture_modal === true) {\n twitter_component.zoom_capture = focused_capture;\n }\n\n // 現在フォーカスされているキャプチャが見える位置までスクロール\n // block: 'nearest' の指定で、上下どちらにスクロールしてもフォーカスされているキャプチャが常に表示されるようになる\n const focused_capture_element =\n twitter_component.$el.querySelector(`img[src=\"${focused_capture.image_url}\"]`).parentElement;\n if (is_repeat) {\n // キーリピート状態ではスムーズスクロールがフォーカスの移動に追いつけずスクロールの挙動がおかしくなるため、\n // スムーズスクロールは無効にしてある\n focused_capture_element.scrollIntoView({block: 'nearest', inline: 'nearest', behavior: 'auto'});\n } else {\n focused_capture_element.scrollIntoView({block: 'nearest', inline: 'nearest', behavior: 'smooth'});\n }\n return true;\n }\n\n // ***** キャプチャを拡大表示する/拡大表示を閉じる *****\n\n if (event.code === 'Enter') {\n\n // Enter キーの押下がプレイヤー側のコメント送信由来のイベントの場合は実行しない\n if (this.is_comment_send_just_did) return false;\n\n // すでにモーダルが開かれている場合は、どのキャプチャが拡大表示されているかに関わらず閉じる\n if (twitter_component.zoom_capture_modal === true) {\n twitter_component.zoom_capture_modal = false;\n return true;\n }\n\n // 現在フォーカスされているキャプチャを取得\n // まだどのキャプチャにもフォーカスされていない場合は実行しない\n const focused_capture = twitter_component.captures.find(capture => capture.focused === true);\n if (focused_capture === undefined) return false;\n\n // モーダルを開き、モーダルで拡大表示するキャプチャとしてセット\n twitter_component.zoom_capture = focused_capture;\n twitter_component.zoom_capture_modal = true;\n return true;\n }\n\n // ***** キャプチャを選択する/選択を解除する *****\n\n if (event.code === 'Space') {\n\n // 現在フォーカスされているキャプチャを取得\n // まだどのキャプチャにもフォーカスされていない場合は実行しない\n const focused_capture = twitter_component.captures.find(capture => capture.focused === true);\n if (focused_capture === undefined) return false;\n\n // 「キャプチャリスト内のキャプチャがクリックされたときのイベント」を呼ぶ\n // 選択されていなければ選択され、選択されていれば選択が解除される\n // キャプチャの枚数制限などはすべて clickCapture() の中で処理される\n twitter_component.clickCapture(focused_capture);\n return true;\n }\n }\n\n // ***** 上下キーでチャンネルを切り替える *****\n\n // キーリピートでない時・Ctrl / Cmd / Shift / Alt キーが一緒に押された時に作動しないように\n // キャプチャ関連のショートカットの後に持ってこないとキャプチャ関連のショートカットが動作しなくなる\n if (is_repeat === false && !event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey) {\n\n // ↑キー: 前のチャンネルに切り替え\n if (event.code === 'ArrowUp') {\n this.is_zapping = true;\n await this.$router.push({path: `/tv/watch/${this.channelsStore.channel.previous.channel_id}`});\n return true;\n }\n // ↓キー: 次のチャンネルに切り替え\n if (event.code === 'ArrowDown') {\n this.is_zapping = true;\n await this.$router.push({path: `/tv/watch/${this.channelsStore.channel.next.channel_id}`});\n return true;\n }\n }\n\n // ***** プレイヤーのショートカットキー *****\n\n // プレイヤーが初期化されていない時・Shift / Alt キーが一緒に押された時に作動しないように\n if (this.player !== null && !event.shiftKey && !event.altKey) {\n\n // Ctrl / Cmd + ↑キー: プレイヤーの音量を上げる\n if ((event.ctrlKey || event.metaKey) && event.code === 'ArrowUp') {\n this.player.volume(this.player.volume() + 0.05);\n return true;\n }\n // Ctrl / Cmd + ↓キー: プレイヤーの音量を下げる\n if ((event.ctrlKey || event.metaKey) && event.code === 'ArrowDown') {\n this.player.volume(this.player.volume() - 0.05);\n return true;\n }\n // Ctrl / Cmd + ←キー: 停止して0.5秒巻き戻し\n if ((event.ctrlKey || event.metaKey) && event.code === 'ArrowLeft') {\n if (this.player.video.paused === false) this.player.video.pause();\n this.player.video.currentTime = this.player.video.currentTime - 0.5;\n return true;\n }\n // Ctrl / Cmd + →キー: 停止して0.5秒早送り\n if ((event.ctrlKey || event.metaKey) && event.code === 'ArrowRight') {\n if (this.player.video.paused === false) this.player.video.pause();\n this.player.video.currentTime = this.player.video.currentTime + 0.5;\n return true;\n }\n }\n\n // プレイヤーが初期化されていない時・Ctrl / Cmd / Alt キーが一緒に押された時に作動しないように\n if (this.player !== null && !event.ctrlKey && !event.metaKey && !event.altKey) {\n\n // Shift + Spaceキー + キーリピートでない時 + Twitter タブ表示時 + キャプチャタブ表示時: 再生/停止\n if (event.shiftKey === true && event.code === 'Space' && is_repeat === false &&\n this.tv_panel_active_tab === 'Twitter' && twitter_component.twitter_active_tab === 'Capture') {\n this.player.toggle();\n return true;\n }\n }\n\n // プレイヤーが初期化されていない時・キーリピートでない時・Ctrl / Cmd / Alt キーが一緒に押された時に作動しないように\n if (this.player !== null && is_repeat === false && !event.ctrlKey && !event.metaKey && !event.altKey) {\n\n // Spaceキー: 再生/停止\n if (event.code === 'Space') {\n this.player.toggle();\n return true;\n }\n // Fキー: フルスクリーンの切り替え\n if (event.code === 'KeyF') {\n this.player.fullScreen.toggle();\n return true;\n }\n // Wキー: ライブストリームの同期\n if (event.code === 'KeyW') {\n this.player.sync();\n return true;\n }\n // Eキー: Picture-in-Picture の表示切り替え\n if (event.code === 'KeyE') {\n if (document.pictureInPictureEnabled) {\n this.player.template.pipButton.click();\n }\n return true;\n }\n // Sキー: 字幕の表示切り替え\n if (event.code === 'KeyS') {\n this.player.subtitle.toggle();\n if (!this.player.subtitle.container.classList.contains('dplayer-subtitle-hide')) {\n this.player.notice(`${this.player.tran('Show subtitle')}`);\n } else {\n this.player.notice(`${this.player.tran('Hide subtitle')}`);\n }\n return true;\n }\n // Dキー: コメントの表示切り替え\n if (event.code === 'KeyD') {\n this.player.template.showDanmaku.click();\n if (this.player.template.showDanmakuToggle.checked) {\n this.player.notice(`${this.player.tran('Show comment')}`);\n } else {\n this.player.notice(`${this.player.tran('Hide comment')}`);\n }\n return true;\n }\n // Cキー: 映像をキャプチャ\n if (event.code === 'KeyC') {\n await this.capture_handler.captureAndSave(false);\n return true;\n }\n // Vキー: 映像を実況コメントを付けてキャプチャ\n if (event.code === 'KeyV') {\n await this.capture_handler.captureAndSave(true);\n return true;\n }\n // Mキー: コメント入力フォームにフォーカス\n if (event.code === 'KeyM') {\n this.player.controller.show();\n this.player.comment.show();\n this.controlDisplayTimer();\n window.setTimeout(() => this.player.template.commentInput.focus(), 100);\n return true;\n }\n }\n }\n return false;\n })();\n\n // 無名関数を実行した後の戻り値が true ならショートカットキーの操作を実行したことになるので、デフォルトのキー操作を封じる\n if (result === true) {\n event.preventDefault();\n }\n };\n\n // ページ上でキーが押されたときのイベントを登録\n document.addEventListener('keydown', this.shortcut_key_handler);\n },\n\n // キャプチャ関連のイベントを初期化する\n initCaptureHandler() {\n\n // キャプチャハンドラーを初期化\n this.capture_handler = new CaptureHandler(this.player, (blob: Blob, filename: string) => {\n // キャプチャが撮られたら、随時 Twitter タブのキャプチャリストに追加する\n (this.$refs.Twitter as InstanceType).addCaptureList(blob, filename);\n });\n\n // キャプチャボタンがクリックされたときのイベント\n // ショートカットからのキャプチャでも同じイベントがトリガーされる\n const capture_button = this.$el.querySelector('.dplayer-icon.dplayer-capture-icon');\n capture_button.addEventListener('click', async () => {\n await this.capture_handler.captureAndSave(false);\n });\n\n // コメント付きキャプチャボタンがクリックされたときのイベント\n // ショートカットからのキャプチャでも同じイベントがトリガーされる\n const comment_capture_button = this.$el.querySelector('.dplayer-icon.dplayer-comment-capture-icon');\n comment_capture_button.addEventListener('click', async () => {\n await this.capture_handler.captureAndSave(true);\n });\n },\n\n\n // 再生セッションを破棄する\n // チャンネルを切り替える際に実行される\n async destroy(is_destroy_player = false, is_zapping_continuously = false) {\n\n // ニコニコ実況セッションを破棄し、コメント受信を終了する\n (this.$refs.Comment as InstanceType).destroy();\n\n // clearInterval() ですべての setInterval(), setTimeout() の実行を止める\n // clearInterval() と clearTimeout() は中身共通なので問題ない\n for (const interval_id of this.interval_ids) {\n window.clearInterval(interval_id);\n }\n\n // コントロール表示制御用タイマーを止める\n window.clearTimeout(this.control_interval_id);\n\n // interval_ids をクリア\n this.interval_ids = [];\n\n // 再びローディング状態にする\n this.is_loading = true;\n\n // プレイヤーの背景を隠す\n this.is_background_display = false;\n\n // プレイヤーの破棄を許可する\n if (this.player !== null) {\n this.player_can_be_destroyed = true;\n }\n\n // イベントソースを閉じる\n if (this.eventsource !== null) {\n this.eventsource.close();\n this.eventsource = null;\n }\n\n // 映像がフェードアウトするアニメーション (0.2秒) 分待ってから実行\n // この 0.2 秒の間に音量をフェードアウトさせる\n // なお、ザッピングでチャンネルを連続で切り替えている場合は実行しない (実行しても意味がないため)\n if (is_zapping_continuously === false) {\n const current_volume = this.player.user.get('volume');\n // 20回 (0.01秒おき) に分けて音量を下げる\n for (let i = 0; i < 20; i++) {\n await Utils.sleep(0.01);\n this.player.video.volume = current_volume * (1 - (i + 1) / 20);\n }\n }\n\n // is_destroy_player が true の時は、ここで DPlayer 自体を破棄する\n // false の時は次の initPlayer() が実行されるまで破棄されない\n // 次のプレイヤーの初期化の直前に前のプレイヤーを破棄することで、プレイヤーの HTML が消えることによるちらつきを防ぐ\n if (is_destroy_player === true && this.player !== null) {\n try {\n this.player.destroy();\n } catch (error) {\n // mpegts.js をうまく破棄できない場合\n if (this.player.plugins.mpegts !== undefined) {\n this.player.plugins.mpegts.destroy();\n }\n }\n this.player_can_be_destroyed = false;\n this.player = null;\n }\n }\n }\n});\n\n","import mod from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Watch.vue?vue&type=script&lang=ts&\"; export default mod; export * from \"-!../../../node_modules/cache-loader/dist/cjs.js??clonedRuleSet-41.use[0]!../../../node_modules/thread-loader/dist/cjs.js!../../../node_modules/babel-loader/lib/index.js!../../../node_modules/ts-loader/index.js??clonedRuleSet-41.use[3]!../../../node_modules/cache-loader/dist/cjs.js??ruleSet[0].use[0]!../../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./Watch.vue?vue&type=script&lang=ts&\"","import { render, staticRenderFns } from \"./Watch.vue?vue&type=template&id=7aded832&scoped=true&\"\nimport script from \"./Watch.vue?vue&type=script&lang=ts&\"\nexport * from \"./Watch.vue?vue&type=script&lang=ts&\"\nimport style0 from \"./Watch.vue?vue&type=style&index=0&id=7aded832&prod&lang=scss&\"\nimport style1 from \"./Watch.vue?vue&type=style&index=1&id=7aded832&prod&lang=scss&scoped=true&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"7aded832\",\n null\n \n)\n\nexport default component.exports","\nimport Vue from 'vue';\nimport VueRouter from 'vue-router';\n\nimport Utils from '@/utils';\nimport Login from '@/views/Login.vue';\nimport NotFound from '@/views/NotFound.vue';\nimport Register from '@/views/Register.vue';\nimport SettingsAccount from '@/views/Settings/Account.vue';\nimport SettingsCaption from '@/views/Settings/Caption.vue';\nimport SettingsCapture from '@/views/Settings/Capture.vue';\nimport SettingsGeneral from '@/views/Settings/General.vue';\nimport SettingsIndex from '@/views/Settings/Index.vue';\nimport SettingsJikkyo from '@/views/Settings/Jikkyo.vue';\nimport SettingsServer from '@/views/Settings/Server.vue';\nimport SettingsTwitter from '@/views/Settings/Twitter.vue';\nimport TVHome from '@/views/TV/Home.vue';\nimport TVWatch from '@/views/TV/Watch.vue';\n\nVue.use(VueRouter);\n\nconst router = new VueRouter({\n\n // History API モード\n mode: 'history',\n\n // ルーティングのベース URL\n base: process.env.BASE_URL,\n\n // ルーティング設定\n routes: [\n {\n path: '/',\n redirect: '/tv/',\n },\n {\n path: '/tv/',\n name: 'TV Home',\n component: TVHome,\n },\n {\n path: '/tv/watch/:channel_id',\n name: 'TV Watch',\n component: TVWatch,\n },\n {\n path: '/settings/',\n name: 'Settings Index',\n component: SettingsIndex,\n beforeEnter: (to, from, next) => {\n\n // スマホ縦画面・スマホ横画面・タブレット縦画面では設定一覧画面を表示する(画面サイズの関係)\n if (Utils.isSmartphoneVertical() || Utils.isSmartphoneHorizontal() || Utils.isTabletVertical()) {\n next(); // 通常通り遷移\n return;\n }\n\n // それ以外の画面サイズでは全般設定にリダイレクト\n next({path: '/settings/general/'});\n }\n },\n {\n path: '/settings/general',\n name: 'Settings General',\n component: SettingsGeneral,\n },\n {\n path: '/settings/caption',\n name: 'Settings Caption',\n component: SettingsCaption,\n },\n {\n path: '/settings/capture',\n name: 'Settings Capture',\n component: SettingsCapture,\n },\n {\n path: '/settings/account',\n name: 'Settings Account',\n component: SettingsAccount,\n },\n {\n path: '/settings/jikkyo',\n name: 'Settings Jikkyo',\n component: SettingsJikkyo,\n },\n {\n path: '/settings/twitter',\n name: 'Settings Twitter',\n component: SettingsTwitter,\n },\n {\n path: '/settings/server',\n name: 'Settings Server',\n component: SettingsServer,\n },\n {\n path: '/login/',\n name: 'Login',\n component: Login,\n },\n {\n path: '/register/',\n name: 'Register',\n component: Register,\n },\n {\n path: '*',\n name: 'NotFound',\n component: NotFound,\n },\n ],\n\n // ページ遷移時のスクロールの挙動の設定\n // ref: https://v3.router.vuejs.org/ja/guide/advanced/scroll-behavior.html\n scrollBehavior (to, from, savedPosition) {\n if (savedPosition) {\n // 戻る/進むボタンが押されたときは保存されたスクロール位置を使う\n return savedPosition;\n } else {\n // それ以外は常に先頭にスクロールする\n return {x: 0, y: 0};\n }\n }\n});\n\nexport default router;\n","/* eslint-disable no-console */\n\nimport { register } from 'register-service-worker';\n\nimport Message from '@/message';\nimport Utils from '@/utils';\n\n\nif (process.env.NODE_ENV === 'production') {\n register(`${process.env.BASE_URL}service-worker.js`, {\n ready() {\n console.log(\n 'App is being served from cache by a service worker.\\n' +\n 'For more details, visit https://goo.gl/AFskqB'\n );\n },\n registered() {\n console.log('Service worker has been registered.');\n },\n cached() {\n console.log('Content has been cached for offline use.');\n },\n updatefound() {\n console.log('New content is downloading.');\n },\n updated(registration: ServiceWorkerRegistration) {\n console.log('New content is available; please refresh.');\n Message.show({\n message: 'クライアントが新しいバージョンに更新されました。5秒後にリロードします。',\n timeout: 10000, // リロードするまで表示し続ける\n });\n // PWA (Service Worker) を更新する\n registration.waiting.postMessage({type: 'SKIP_WAITING'});\n registration.waiting.addEventListener('statechange', async (event) => {\n if ((event.target as ServiceWorker).state === 'activated') {\n await Utils.sleep(4); // activated になるまで少し時間がかかるので、1秒減らして4秒待つ\n location.reload(true);\n }\n });\n },\n offline() {\n console.log('No internet connection found. App is running in offline mode.');\n },\n error(error) {\n console.error('Error during service worker registration:', error);\n }\n });\n}\n","\nimport { Icon } from '@iconify/vue2';\nimport { createPinia, PiniaVuePlugin } from 'pinia';\nimport { polyfill as SeamlessScrollPolyfill } from 'seamless-scroll-polyfill';\nimport VTooltip from 'v-tooltip';\nimport Vue from 'vue';\nimport VueVirtualScroller from 'vue-virtual-scroller';\nimport 'vue-virtual-scroller/dist/vue-virtual-scroller.css';\nimport VuetifyMessageSnackbar from 'vuetify-message-snackbar';\nimport 'v-tooltip/dist/v-tooltip.css';\n\nimport App from '@/App.vue';\nimport VTabItem from '@/components/Vuetify/VTabItem';\nimport VTabs from '@/components/Vuetify/VTabs';\nimport VTabsItems from '@/components/Vuetify/VTabsItems';\nimport vuetify from '@/plugins/vuetify';\nimport router from '@/router';\nimport useSettingsStore, { setLocalStorageSettings } from '@/store/SettingsStore';\nimport '@/service-worker';\nimport Utils from '@/utils';\n\n\n// スムーズスクロール周りの API の polyfill を適用\n// Element.scrollInfoView() のオプション指定を使うために必要\nSeamlessScrollPolyfill();\n\n// Production Tip を非表示にする\nVue.config.productionTip = false;\n\n// 常に Vue.js devtools を有効にする\nVue.config.devtools = true;\n\n// Pinia を使う\n// ref: https://pinia.vuejs.org/cookbook/options-api.html\nVue.use(PiniaVuePlugin);\nconst pinia = createPinia();\n\n// vue-virtual-scroller を使う\nVue.use(VueVirtualScroller);\n\n// vuetify-message-snackbar を使う\n// マイナーな OSS(しかも中国語…)だけど、Snackbar を関数で呼びたかったのでちょうどよかった\n// ref: https://github.com/thinkupp/vuetify-message-snackbar\nVue.use(VuetifyMessageSnackbar, {\n // 画面上に配置しない\n top: false,\n // 画面下に配置する\n bottom: true,\n // デフォルトの背景色\n color: '#433532',\n // ダークテーマを適用する\n dark: true,\n // 影 (Elevation) の設定\n elevation: 8,\n // 2.5秒でタイムアウト\n timeout: 2500,\n // 要素が非表示になった後に DOM から要素を削除する\n autoRemove: true,\n // 閉じるボタンのテキスト\n closeButtonContent: '閉じる',\n // Vuetify のインスタンス\n vuetifyInstance: vuetify,\n});\n\n// VTooltip を使う\n// タッチデバイスでは無効化する\n// ref: https://v-tooltip.netlify.app/guide/config.html#default-values\nconst trigger = Utils.isTouchDevice() ? [] : ['hover', 'focus', 'touch'];\nVTooltip.options.themes.tooltip.showTriggers = trigger;\nVTooltip.options.themes.tooltip.hideTriggers = trigger;\nVTooltip.options.themes.tooltip.delay.show = 0;\nVTooltip.options.offset = [0, 7];\nVue.use(VTooltip);\n\n// Iconify(アイコン)のグローバルコンポーネント\nVue.component('Icon', Icon);\n\n// VTabItem / VTabs / VTabsItems の挙動を改善するグローバルコンポーネント\nVue.component('v-tab-item-fix', VTabItem);\nVue.component('v-tabs-fix', VTabs);\nVue.component('v-tabs-items-fix', VTabsItems);\n\n// Vue を初期化\n(window as any).KonomiTVVueInstance = new Vue({\n pinia,\n router,\n vuetify,\n render: h => h(App),\n}).$mount('#app');\n\n// 設定データをサーバーにアップロード中かどうか\nlet is_uploading_settings = false;\n\n// 設定データの変更を監視する\nconst settings_store = useSettingsStore();\nsettings_store.$subscribe(async () => {\n\n // 設定データをアップロード中の場合は何もしない\n if (is_uploading_settings === true) {\n return;\n }\n\n // 設定データを LocalStorage に保存\n console.log('Client Settings Changed:', settings_store.settings);\n setLocalStorageSettings(settings_store.settings);\n\n // 設定データをサーバーに同期する (ログイン時かつ同期が有効な場合のみ)\n await settings_store.syncClientSettingsToServer();\n\n}, {detached: true});\n\n// ログイン時かつ設定の同期が有効な場合、ページ遷移に関わらず、常に3秒おきにサーバーから設定を取得する\n// 初回のページレンダリングに間に合わないのは想定内(同期の完了を待つこともできるが、それだと表示速度が遅くなるのでしょうがない)\nwindow.setInterval(async () => {\n if (Utils.getAccessToken() !== null && settings_store.settings.sync_settings === true) {\n\n // 設定データをサーバーにアップロード\n is_uploading_settings = true;\n await settings_store.syncClientSettingsFromServer();\n is_uploading_settings = false;\n\n // 設定データを LocalStorage に保存\n setLocalStorageSettings(settings_store.settings);\n }\n}, 3 * 1000); // 3秒おき\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","var deferred = [];\n__webpack_require__.O = function(result, chunkIds, fn, priority) {\n\tif(chunkIds) {\n\t\tpriority = priority || 0;\n\t\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar chunkIds = deferred[i][0];\n\t\tvar fn = deferred[i][1];\n\t\tvar priority = deferred[i][2];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every(function(key) { return __webpack_require__.O[key](chunkIds[j]); })) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = function(module) {\n\tvar getter = module && module.__esModule ?\n\t\tfunction() { return module['default']; } :\n\t\tfunction() { return module; };\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// define __esModule on exports\n__webpack_require__.r = function(exports) {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t143: 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = function(chunkId) { return installedChunks[chunkId] === 0; };\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = function(parentChunkLoadingFunction, data) {\n\tvar chunkIds = data[0];\n\tvar moreModules = data[1];\n\tvar runtime = data[2];\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some(function(id) { return installedChunks[id] !== 0; })) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunkKonomiTV\"] = self[\"webpackChunkKonomiTV\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","// startup\n// Load entry module and return exports\n// This entry module depends on other loaded chunks and execution need to be delayed\nvar __webpack_exports__ = __webpack_require__.O(undefined, [998], function() { return __webpack_require__(9537); })\n__webpack_exports__ = __webpack_require__.O(__webpack_exports__);\n"],"names":["_c","_self","attrs","staticRenderFns","script","component","render","VTabItem","h","props","name","this","computedTransition","on","beforeEnter","onBeforeTransition","afterEnter","onAfterTransition","enterCancelled","onTransitionCancelled","beforeLeave","afterLeave","leaveCancelled","enter","onEnter","genWindowItem","VTabsBar","data","items","methods","register","item","activeItem","internalIndex","push","sort","a","b","index_a","$slots","default","findIndex","element","$vnode","key","index_b","$on","onClick","mandatory","selectedValues","length","updateMandatory","updateItem","indexOf","undefined","updateInternalValue","unregister","constructor","super","options","call","VTabs","genBar","slider","style","height","convertToUnit","activeClass","centerActive","dark","light","optional","mobileBreakpoint","nextIcon","prevIcon","showArrows","value","internalValue","callSlider","change","val","ref","setTextColor","computedColor","setBackgroundColor","backgroundColor","$createElement","genSlider","VTabsItems","updateReverse","oldVal","itemsLength","lastIndex","continuous","Vue","Vuetify","VSnackbar","VBtn","VIcon","theme","themes","primary","secondary","twitter","base","lighten1","lighten2","gray","black","background","lighten3","text","darken1","darken2","darken3","customProperties","Utils","static","localStorage","getItem","access_token","setItem","removeItem","test","navigator","userAgent","blob","filename","blob_url","URL","createObjectURL","link","document","createElement","download","href","click","revokeObjectURL","content","html_escape_table","replace","match","popupSizeWidth","popupSizeHeight","window","screen","posTop","posLeft","width","class_name","activeElement","classList","contains","matchMedia","matches","Math","floor","seconds","Promise","resolve","setTimeout","Date","now","Object","prototype","toString","slice","toLowerCase","escapeHTML","pattern","version","process","api_base_url","location","protocol","host","ChannelUtils","channel_id","result","groups","channel_type","toUpperCase","e","channel_force","success","message","_a","KonomiTVVueInstance","$message","info","warning","error","show","axios_instance","axios","interceptors","request","use","config","baseURL","url","startsWith","headers","timeout","Users","user_create_request","response","APIClient","Message","username","password","URLSearchParams","responseType","user_update_request","icon","form_data","FormData","append","useUserStore","defineStore","state","is_logged_in","user","user_icon_url","getters","user_niconico_icon_url","niconico_user_id","user_id_slice","actions","async","console","log","login","silent","logout","fetchUser","settings_store","useSettingsStore","settings","sync_settings","force","catch","AxiosError","status","Error","detail","is_error","NaN","is_success","assign","method","template","user_store","Number","isNaN","Settings","sync_settings_keys","default_settings","pinned_channel_ids","showed_panel_last_time","selected_twitter_account_id","saved_twitter_hashtags","tv_streaming_quality","tv_data_saver_mode","tv_low_latency_mode","panel_display_state","tv_panel_active_tab","tv_channel_selection_requires_alt_key","caption_font","always_border_caption_text","specify_caption_opacity","caption_opacity","tv_show_superimpose","capture_copy_to_clipboard","capture_save_mode","capture_caption_mode","comment_speed_rate","comment_font_size","close_comment_form_after_sending","muted_comment_keywords","muted_niconico_user_ids","mute_vulgar_comments","mute_abusive_discriminatory_prejudiced_comments","mute_big_size_comments","mute_fixed_comments","mute_colored_comments","mute_consecutive_same_characters_comments","fold_panel_after_sending_tweet","reset_hashtag_when_program_switches","auto_add_watching_channel_hashtag","twitter_active_tab","tweet_hashtag_position","tweet_capture_watermark_position","getLocalStorageSettings","JSON","parse","setLocalStorageSettings","stringify","getNormalizedSettings","new_settings","default_settings_key","keys","file","settings_json","syncClientSettingsToServer","default_settings_modified","getSyncableClientSettings","sync_settings_key","settings_server","settings_server_key","settings_server_value","entries","CommentUtils","color","color_table","position","size","comment_mail","commands","split","command","parsed_color","getCommentColor","parsed_position","getCommentPosition","parsed_size","getCommentSize","comment","user_id","includes","special_command_comments_pattern","mute_vulgar_comments_pattern","mute_abusive_discriminatory_prejudiced_comments_pattern","mute_consecutive_same_characters_comments_pattern","muted_comment_keyword","endsWith","RegExp","annoying_statistical_comments_pattern","Buffer","PlayerUtils","background_count","random","padStart","canPlayType","ProgramUtils","program","mark","pattern1","pattern2","dayjs","isSameOrAfter","isSameOrBefore","isBetween","pause_time_start","hour","minute","second","pause_time_end","pause_time_start_23","pause_time_end_23","default_value","progress","diff","start_time","duration","is_short","end_time","format","staticClass","$event","model","callback","expression","_vm","password_showing","directives","rawName","_setup","_v","staticStyle","class","Version","useVersionStore","server_version_info","last_updated_at","client_version","server_version","_b","latest_version","is_client_develop_version","is_server_develop_version","is_update_available","is_version_mismatch","fetchServerVersion","version_info","components","BottomNavigation","computed","mapStores","versionStore","Header","Navigation","is_form_dense","userStore","$router","path","_setupProxy","_m","password_validation","username_validation","$refs","validate","is_admin","overrideServerSettingsFromClient","sync_settings_dialog","preventDefault","settings_username","settings_icon","$$v","settings_password_showing","settings_password","scopedSlots","account_delete_confirm_dialog","SettingsBase","is_loading","settings_username_validation","settings_password_validation","watch","settingsStore","sync_settings_json","server_sync_settings","server_sync_settings_json","syncClientSettingsFromServer","update_type","updateUser","updateUserIcon","deleteUser","$set","isHEVCVideoSupported","import_settings_file","QUALITY_H264","QUALITY_H265","created","immediate","handler","exportSettings","settings_json_blob","Blob","type","importClientSettings","go","resetClientSettings","comment_mute_settings_modal","muted_comment_keyword_match_type","prop","event","showing","Boolean","required","interval_timer_id","$emit","Niconico","authorization_url","CommentMuteSettings","hash","params","get","authorization_status","parseInt","authorization_detail","onOAuthCallbackReceived","history","replaceState","popup_window","open","onMessage","closed","close","removeEventListener","addEventListener","twitter_account","loginTwitterAccountWithPasswordForm","twitter_password_auth_dialog","twitter_screen_name","twitter_password_showing","twitter_password","Twitter","twitter_password_auth_request","screen_name","captures","tweet_capture","is_twitter_password_auth_sending","twitter_form","current_twitter_account","twitter_accounts","updated_at","reset","Array","from","channels_type","channels","id","removePinnedChannel","stopPropagation","domProps","_s","IProgramDefault","network_id","service_id","event_id","title","description","is_free","genre","video_type","video_codec","video_resolution","primary_audio_type","primary_audio_language","primary_audio_sampling_rate","secondary_audio_type","secondary_audio_language","secondary_audio_sampling_rate","IChannelDefault","transport_stream_id","remocon_id","channel_number","channel_name","channel_comment","is_subchannel","is_radiochannel","is_display","viewers","program_present","program_following","Channels","useChannelsStore","channels_list","GR","BS","CS","CATV","SKY","STARDIGIO","is_channels_list_initial_updated","is_showing_live","channel","getChannelType","previous","current","next","current_channel_index","IProgramError","IChannelError","previous_channel_index","index","next_channel_index","channels_list_with_pinned","Map","set","_d","_e","_f","_g","delete","has","channels_list_with_pinned_for_watch","getChannel","find","getChannelByRemoconID","updateChannel","update","tab","interval_ids","residue_second","getSeconds","channelsStore","setInterval","beforeDestroy","interval_id","clearInterval","addPinnedChannel","splice","isPinnedChannel","controlDisplayTimer","modifiers","backgroundImage","is_panel_display","player","shortcut_key_modal","shortcut_key_column_name","shortcut_key_column","shortcut_keys","key_name","_l","getProgramTime","getProgramProgress","is_comment_list_dropdown_display","fn","active","time","isTouchDevice","initialize_failed_message","is_manual_scroll","LiveCommentManager","on_initial_comments_received","on_comment_received","watch_session","comment_session","vpos_base_timestamp","keep_seat_interval_id","abort_controller","AbortController","watch_session_result","initWatchSession","initCommentSession","is_disconnect_message_received","watch_session_info","notice","WebSocket","audience_token","send","signal","code","reconnect","readyState","OPEN","keepIntervalSec","disconnect_reason","reason","vposBaseTime","valueOf","threadId","message_server_url","messageServer","uri","thread_id","your_post_key","yourPostKey","comment_session_info","initial_comments_buffer","initial_comments_received","ping","thread","threadkey","res_from","resultcode","chat","yourpost","parseCommentCommand","mail","isMutedComment","comment_data","no","date","my_post","buffered_end","video","buffered","end","comment_delay_time","currentTime","paused","danmaku","draw","sendComment","position_table","vpos","round","abort","warn","destroy","initSession","is_auto_scrolling","comment_list","comment_list_element","comment_list_dropdown_top","comment_list_dropdown_comment","live_comment_manager","visibilitychange_listener","resize_observer","resize_observer_element","mounted","$el","querySelector","is_user_scrolling","onmousedown","x","clientX","getBoundingClientRect","left","clientWidth","onmouseup","on_user_scrolling","is_dragging","ontouchstart","ontouchend","ontouchmove","onwheel","onscroll","scrollTop","offsetHeight","scrollHeight","unobserve","showCommentListDropdown","comment_list_wrapper_rect","comment_list_wrapper","comment_list_dropdown_height","comment_button_rect","currentTarget","top","hideCommentListDropdown","filter","addMutedKeywords","addMutedNiconicoUserIds","addMutedNiconicoUserIDs","smooth","scrollTo","behavior","initReserveObserver","animation_timeout_id","on_resize","video_element","comment_area_element","clientHeight","letter_box_height","threshold","comment_area_vertical_margin","comment_area_width","comment_area_height","gcd","y","gcd_result","comment_area_height_aspect","transition","setProperty","clearTimeout","removeProperty","ResizeObserver","observe","comment_list_buffer","max_comment_count","initial_comments","scrollCommentList","visibilityState","max","comment_list_and_buffer_length","niconico_user_premium","decorateProgramInfo","getAttribute","genre_index","major","middle","getChannelForceType","detail_text","detail_heading","URLtoLink","zoom_capture_modal","capture","clickCapture","target","tweet_hashtag","is_tweet_hashtag_form_focused","updateTweetLetterCount","tweet_text","is_tweet_text_form_focused","is_logged_in_twitter","tweet_letter_count","editing","hashtag","updateSelectedTwitterAccount","draggable","is_virtual_keyboard_display","selected_twitter_account","is_twitter_account_list_display","map","is_hashtag_list_display","zoom_capture","captures_element","tweet_captures","is_tweet_sending","some","twitter_account_index","formatHashtag","image_url","deep","pasteClipboardData","clipboard_item","clipboardData","getAsFile","addCaptureList","clickHashtagListButton","clickHashtag","clickAccountButton","fullscreenElement","exitFullscreen","selected","shift","focused","$nextTick","image_bitmap","createImageBitmap","canvas","OffscreenCanvas","context","getContext","alpha","desynchronized","willReadFrequently","drawImage","font","fillStyle","shadowColor","shadowBlur","shadowOffsetX","shadowOffsetY","textAlign","textBaseline","fillText","convertToBlob","quality","toBlob","getChannelHashtag","from_hashtag_list","tweet_hashtag_array","trim","replaceAll","channel_hashtag","join","new_tweet_captures","drawProgramTitleOnCapture","then","blur","Captures","web_font_noto_sans_base64","web_font_open_sans_base64","CaptureHandler","captured_callback","player_container","container","insertAdjacentHTML","comment_capture_button","capture_button","canvas_context","videoWidth","videoHeight","web_font_noto_sans_url","web_font_open_sans_url","base64_font_prefix","web_font_noto_sans","web_font_open_sans","with_comments","total_time","channels_store","addHighlight","filename_base","filename_caption","caption_canvas","plugins","aribb24Caption","getRawCanvas","superimpose_canvas","aribb24Superimpose","is_caption_showing","isShowing","isPresent","is_superimpose_showing","caption_text","getTextContent","exif_options","_h","_k","_j","is_caption_composited","is_comment_composited","export_and_save","exportToBlob","setEXIFDataToCapture","capture_normal","capture_caption","promises","comments_image","createCommentsImage","drawComments","filename_real","all","bitmap_canvas","transferFromImageBitmap","removeHighlight","copyBlobToClipboard","convertBlobToPng","add","remove","html","svg","image","Image","src","encodeURIComponent","decode","comments_html","outerHTML","querySelectorAll","commentsHTMLtoSVGImage","offsetWidth","draw_scale_ratio","draw_height","HTMLCanvasElement","reject","captured_playback_position","json","captured_at","datetime","exif","piexif","TagValues","ImageIFD","XResolution","YResolution","ResolutionUnit","YCbCrPositioning","DateTime","Software","XPComment","ExifIFD","ExifVersion","ComponentsConfiguration","FlashpixVersion","ColorSpace","DateTimeOriginal","DateTimeDigitized","exif_string","dump","blob_string","reader","FileReader","onload","onerror","readAsBinaryString","blob_string_new","insert","buffer","Uint8Array","charCodeAt","PLAYBACK_BUFFER_SEC_LOW_LATENCY","PLAYBACK_BUFFER_SEC","Channel","Comment","Program","background_url","is_video_buffering","is_background_display","is_control_display","is_fullscreen","is_ime_composing","is_comment_send_just_did","control_interval_id","is_zapping","is_zapping_continuously","player_can_be_destroyed","is_mpegts_supported","mpegts","romsounds_context","romsounds_buffers","eventsource","fullscreen_handler","capture_handler","shortcut_key_handler","shortcut_key_pressed_at","shortcut_key_list","left_column","icon_height","shortcuts","right_column","unshift","$route","virtualKeyboard","overlaysContent","ongeometrychange","boundingRect","init","AudioContext","audio_data","decodeAudioData","beforeRouteUpdate","to","destroy_promise","new_channel","old_channel","twitter_component","old_channel_hashtag","generatePlayerBackgroundURL","initPlayer","initEventHandler","initCaptureHandler","initShortcutKeyHandler","audioItem","liveLLHLSForKonomiTV","audioValue","textContent","tran","switchPrimaryAudio","artwork","sizes","mediaSession","metadata","MediaMetadata","artist","setPositionState","playbackRate","setActionHandler","play","pause","is_player_event","is_touch_device","controller","hide","setting","isShow","playback_buffer_sec","DPlayer","lang","live","liveSyncMinBufferSize","loop","airplay","autoplay","hotkey","screenshot","volume","defaultQuality","qualities","hevc_prefix","speedRate","fontSize","apiBackend","read","pluginOptions","enableWorker","enableStashBuffer","liveSync","liveSyncMaxLatency","liveSyncTargetLatency","liveSyncPlaybackRate","aribb24","normalFont","forceStrokeColor","forceBackgroundColor","opacity","drcsReplacement","enableRawCanvas","useStroke","usePUA","PRACallback","resume","buffer_source_node","createBufferSource","gain_node","createGain","connect","destination","gain","start","subtitle","setAutoHide","commentInput","settingOriginPanel","settingOriginPanelHeight","settingBox","clipPath","fullscreen_container","fullScreen","isFullScreen","onfullscreenchange","webkitFullscreenElement","cancel","requestFullscreen","webkitRequestFullscreen","orientation","lock","webkitExitFullscreen","unlock","on_play_or_pause","sync","crossOrigin","error_type","reload","force_play","pause_icon","playButton","innerHTML","mobilePlayButton","on_canplay","oncanplay","oncanplaythrough","get_playback_buffer_sec","current_playback_buffer_sec","recover","current_volume","eventsource_url","EventSource","event_raw","clients_count","load","pictureInPictureElement","exitPictureInPicture","requestPictureInPicture","switchVideo","clear","tweet_form_element","tag","tagName","editable","is_repeat","repeat","focus","scrollLeft","ctrlKey","metaKey","shiftKey","altKey","switch_channel_type","switch_remocon_id","switch_channel","focused_capture_index","focused_capture","focused_capture_element","parentElement","scrollIntoView","block","inline","toggle","pictureInPictureEnabled","pipButton","showDanmaku","showDanmakuToggle","checked","captureAndSave","is_destroy_player","i","VueRouter","router","mode","routes","redirect","TVHome","TVWatch","SettingsIndex","SettingsGeneral","SettingsCaption","SettingsCapture","SettingsAccount","SettingsJikkyo","SettingsTwitter","SettingsServer","Login","Register","NotFound","scrollBehavior","savedPosition","ready","registered","cached","updatefound","updated","registration","waiting","postMessage","offline","SeamlessScrollPolyfill","PiniaVuePlugin","pinia","createPinia","VueVirtualScroller","VuetifyMessageSnackbar","bottom","elevation","autoRemove","closeButtonContent","vuetifyInstance","vuetify","trigger","VTooltip","Icon","App","$mount","is_uploading_settings","$subscribe","detached","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","exports","module","__webpack_modules__","m","deferred","O","chunkIds","priority","notFulfilled","Infinity","fulfilled","j","every","r","n","getter","__esModule","d","definition","o","defineProperty","enumerable","g","globalThis","Function","obj","hasOwnProperty","Symbol","toStringTag","installedChunks","chunkId","webpackJsonpCallback","parentChunkLoadingFunction","moreModules","runtime","chunkLoadingGlobal","self","forEach","bind","__webpack_exports__"],"sourceRoot":""} \ No newline at end of file diff --git a/client/dist/index.html b/client/dist/index.html index f3c93748..503b3467 100644 --- a/client/dist/index.html +++ b/client/dist/index.html @@ -1 +1 @@ -KonomiTV
\ No newline at end of file +KonomiTV
\ No newline at end of file diff --git a/client/dist/service-worker.js b/client/dist/service-worker.js index d6186997..56232018 100644 --- a/client/dist/service-worker.js +++ b/client/dist/service-worker.js @@ -1,2 +1,2 @@ -if(!self.define){let s,e={};const a=(a,r)=>(a=new URL(a+".js",r).href,e[a]||new Promise((e=>{if("document"in self){const s=document.createElement("script");s.src=a,s.onload=e,document.head.appendChild(s)}else s=a,importScripts(a),e()})).then((()=>{let s=e[a];if(!s)throw new Error(`Module ${a} didn’t register its module`);return s})));self.define=(r,i)=>{const d=s||("document"in self?document.currentScript.src:"")||location.href;if(e[d])return;let c={};const o=s=>a(s,d),n={module:{uri:d},exports:c,require:o};e[d]=Promise.all(r.map((s=>n[s]||o(s)))).then((s=>(i(...s),c)))}}define(["./workbox-ddce758e"],(function(s){"use strict";s.setCacheNameDetails({prefix:"KonomiTV"}),self.addEventListener("message",(s=>{s.data&&"SKIP_WAITING"===s.data.type&&self.skipWaiting()})),s.precacheAndRoute([{url:"/assets/css/app.de64fc4c.css",revision:null},{url:"/assets/css/chunk-vendors.1de61f0e.css",revision:null},{url:"/assets/images/account-icon-default.png",revision:"3840f879e0ddf77549f4035ae72e8f6b"},{url:"/assets/images/icon.svg",revision:"63abc49a99bd463af26e73cec607771d"},{url:"/assets/images/icons/apple-touch-icon.png",revision:"a1ff224fdbecfd10c117cd6172799b94"},{url:"/assets/images/icons/favicon-16px.png",revision:"66d1179e73198777a49235a76619a093"},{url:"/assets/images/icons/favicon-32px.png",revision:"85e6e77bb3362197cf564bf9b21ebe12"},{url:"/assets/images/icons/favicon.svg",revision:"1bf40917c217fd567119c219ebabe4b9"},{url:"/assets/images/icons/icon-192px.png",revision:"cc3f0142a77651214f66f0a725253521"},{url:"/assets/images/icons/icon-512px.png",revision:"37175521e6de680e90740ead2506f9fd"},{url:"/assets/images/icons/icon-maskable-192px.png",revision:"291866775902df321181d8dbc66c0d22"},{url:"/assets/images/icons/icon-maskable-512px.png",revision:"d105aac16603bc9e5349fba31bf71cfd"},{url:"/assets/images/logo.svg",revision:"83079d38a7a118e1c80fe28d139991d8"},{url:"/assets/images/player-backgrounds/01.jpg",revision:"14d74db9eb062b39dc128daeba77cb63"},{url:"/assets/images/player-backgrounds/02.jpg",revision:"98e077363a5eec17da30acef5038f924"},{url:"/assets/images/player-backgrounds/03.jpg",revision:"e75e4fc34090286e347cebf12c74b1b8"},{url:"/assets/images/player-backgrounds/04.jpg",revision:"714dd3c050c09a16236f2424c548c83f"},{url:"/assets/images/player-backgrounds/05.jpg",revision:"717125c34121b326e8f90773565f59ca"},{url:"/assets/images/player-backgrounds/06.jpg",revision:"aa3b22785383baf67ad6d53fee94ed1c"},{url:"/assets/images/player-backgrounds/07.jpg",revision:"dc9937f7a374b99981cb0d6c9a642e56"},{url:"/assets/images/player-backgrounds/08.jpg",revision:"b6cedbf1da35814fbf784591380fde62"},{url:"/assets/images/player-backgrounds/09.jpg",revision:"e989450375d6954b37b066a1cec3ad35"},{url:"/assets/images/player-backgrounds/10.jpg",revision:"417128b6120078997139b44ee2c73dbd"},{url:"/assets/images/player-backgrounds/11.jpg",revision:"8c173e2d5980e09dc7b0e36e97b8f189"},{url:"/assets/images/player-backgrounds/12.jpg",revision:"97231a4813562229cc55d4516cb85350"},{url:"/assets/images/player-backgrounds/13.jpg",revision:"6efbebd72cadf7bdd59a0ad5325662d7"},{url:"/assets/images/player-backgrounds/14.jpg",revision:"54d47c83175ed7f11697a2cb3e54e3b1"},{url:"/assets/images/player-backgrounds/15.jpg",revision:"e9cb581540c06a770d299dede678ff0c"},{url:"/assets/images/player-backgrounds/16.jpg",revision:"b7e7ddc4ae9ba3811f3d5c0ae39a073f"},{url:"/assets/images/player-backgrounds/17.jpg",revision:"d363a4b8256115c7505a420ca6a55aae"},{url:"/assets/images/player-backgrounds/18.jpg",revision:"6c4e11b735bf6c95dfa5d47c3ae8e2e2"},{url:"/assets/images/player-backgrounds/19.jpg",revision:"7fdf1e54a13c7e9d34ceb170fb47c26a"},{url:"/assets/images/player-backgrounds/20.jpg",revision:"119ef99d06f809582244c2014ba005aa"},{url:"/assets/images/player-backgrounds/21.jpg",revision:"b83a101c3a856de1728790666e4c0040"},{url:"/assets/images/player-backgrounds/22.jpg",revision:"e6575b88d5aa774dc9b3c53e334c7c04"},{url:"/assets/images/player-backgrounds/23.jpg",revision:"c78d6d5548d8e2ed7b6681a6a29f75bb"},{url:"/assets/images/player-backgrounds/24.jpg",revision:"1da1420c684e6e51a0301b83544cf08c"},{url:"/assets/images/player-backgrounds/25.jpg",revision:"aa51d71045e5f5cc9d3b89daa344b917"},{url:"/assets/images/player-backgrounds/26.jpg",revision:"a8deb2d94eb69f1ccaaede00ba5bb6b7"},{url:"/assets/images/player-backgrounds/27.jpg",revision:"5dde7e046f56139835c5db0c397ea0bd"},{url:"/assets/images/player-backgrounds/28.jpg",revision:"a8027e60652ba8b43f436aba7895a82c"},{url:"/assets/images/player-backgrounds/29.jpg",revision:"e0b12e01312c0e627fb133726e44da8e"},{url:"/assets/images/player-backgrounds/30.jpg",revision:"b1841274afaa34e3f2c73a0bf6546c83"},{url:"/assets/images/player-backgrounds/31.jpg",revision:"89a95df22ca39eb75cf5be6ba869223e"},{url:"/assets/images/player-backgrounds/32.jpg",revision:"04aae5ba779d6f5637e40c9da4952e57"},{url:"/assets/images/player-backgrounds/33.jpg",revision:"cb273547824cbd6adbd3dc4a19e3741c"},{url:"/assets/images/player-backgrounds/34.jpg",revision:"772bdfc97346e0f4466db4f23aaa986f"},{url:"/assets/images/player-backgrounds/35.jpg",revision:"08635247d80c6eada10efd122a0233ae"},{url:"/assets/images/player-backgrounds/36.jpg",revision:"d941cbfec1db86258d3131c664c6c606"},{url:"/assets/images/player-backgrounds/37.jpg",revision:"4f79166b5886629699c8914996dae8ec"},{url:"/assets/images/player-backgrounds/38.jpg",revision:"428d6030a438a79fda9c3b891c49ab7e"},{url:"/assets/images/player-backgrounds/39.jpg",revision:"81a7227fee4c963573dbd748066e79e4"},{url:"/assets/images/player-backgrounds/40.jpg",revision:"e59c8f21613f90767eacfcf35a1827d2"},{url:"/assets/images/player-backgrounds/41.jpg",revision:"63aeaea733070316bff748bc113a4a46"},{url:"/assets/images/player-backgrounds/42.jpg",revision:"d1875f1d0349c573d3e8cb91f2aadc33"},{url:"/assets/images/player-backgrounds/43.jpg",revision:"e64ed14afdc88195682716c677108b80"},{url:"/assets/images/player-backgrounds/44.jpg",revision:"0c29f86f731632a6ac52703b68ffa274"},{url:"/assets/images/player-backgrounds/45.jpg",revision:"6b061167c6e71f473ee9adc45a7dbf7c"},{url:"/assets/images/player-backgrounds/46.jpg",revision:"d24c8fbd847cba6e0e99c85218daa430"},{url:"/assets/images/player-backgrounds/47.jpg",revision:"9a907a8ecadc4889f604b0a00564d1f9"},{url:"/assets/images/player-backgrounds/48.jpg",revision:"aaf24d179484067bf4bab284c52456dd"},{url:"/assets/images/player-backgrounds/49.jpg",revision:"0d6cffc1fe9c516400a904f8cea606b3"},{url:"/assets/images/player-backgrounds/50.jpg",revision:"dde138765e318791b3e236ab985f9e98"},{url:"/assets/js/app.8188c78a.js",revision:null},{url:"/assets/js/chunk-vendors.d6487c76.js",revision:null},{url:"/assets/romsounds/01.wav",revision:"4187b218123ba4ff5de4e48ad3ee7778"},{url:"/assets/romsounds/02.wav",revision:"a6e40866a7da83a5a6a77c62686b2fa6"},{url:"/assets/romsounds/03.wav",revision:"30f5d254ec6c10bc37f0584e6cb2d0ed"},{url:"/assets/romsounds/04.wav",revision:"626bbd8f569576f18fba702740d731c5"},{url:"/assets/romsounds/05.wav",revision:"dbfbd7f4e2e7670f47dac6f52de3fd98"},{url:"/assets/romsounds/06.wav",revision:"5e68fa08d3621ab451a6daf1d52803b9"},{url:"/assets/romsounds/07.wav",revision:"b17f57be56bb2141660d2a18a497cf69"},{url:"/assets/romsounds/08.wav",revision:"88b1bed69315e657ecaa6e7cdaa032c5"},{url:"/assets/romsounds/09.wav",revision:"39dd16fc0f20240d5347448f9703e42a"},{url:"/assets/romsounds/10.wav",revision:"e0a34f995f013843fb5e552c2dc78a03"},{url:"/assets/romsounds/11.wav",revision:"4b6fd4f4bddcee2ad1987e4c82da9476"},{url:"/assets/romsounds/12.wav",revision:"e0e67a86607a7ad8457c4adefbac50e9"},{url:"/assets/romsounds/13.wav",revision:"d6c8ef577228462c7b90e677396ca652"},{url:"/assets/romsounds/14.wav",revision:"d72e6dd844260adc6db71a04d3763d07"},{url:"/index.html",revision:"acfeb175ea1525d95d2caeec4389fda9"},{url:"/manifest.json",revision:"c1a7314b537716c7416fe56300a68a54"},{url:"/robots.txt",revision:"d98e9ec5fafad691048cbb603b5b0e71"}],{}),s.cleanupOutdatedCaches()})); +if(!self.define){let s,e={};const a=(a,r)=>(a=new URL(a+".js",r).href,e[a]||new Promise((e=>{if("document"in self){const s=document.createElement("script");s.src=a,s.onload=e,document.head.appendChild(s)}else s=a,importScripts(a),e()})).then((()=>{let s=e[a];if(!s)throw new Error(`Module ${a} didn’t register its module`);return s})));self.define=(r,i)=>{const c=s||("document"in self?document.currentScript.src:"")||location.href;if(e[c])return;let d={};const o=s=>a(s,c),n={module:{uri:c},exports:d,require:o};e[c]=Promise.all(r.map((s=>n[s]||o(s)))).then((s=>(i(...s),d)))}}define(["./workbox-ddce758e"],(function(s){"use strict";s.setCacheNameDetails({prefix:"KonomiTV"}),self.addEventListener("message",(s=>{s.data&&"SKIP_WAITING"===s.data.type&&self.skipWaiting()})),s.precacheAndRoute([{url:"/assets/css/app.114f8e66.css",revision:null},{url:"/assets/css/chunk-vendors.1de61f0e.css",revision:null},{url:"/assets/images/account-icon-default.png",revision:"3840f879e0ddf77549f4035ae72e8f6b"},{url:"/assets/images/icon.svg",revision:"63abc49a99bd463af26e73cec607771d"},{url:"/assets/images/icons/apple-touch-icon.png",revision:"a1ff224fdbecfd10c117cd6172799b94"},{url:"/assets/images/icons/favicon-16px.png",revision:"66d1179e73198777a49235a76619a093"},{url:"/assets/images/icons/favicon-32px.png",revision:"85e6e77bb3362197cf564bf9b21ebe12"},{url:"/assets/images/icons/favicon.svg",revision:"1bf40917c217fd567119c219ebabe4b9"},{url:"/assets/images/icons/icon-192px.png",revision:"cc3f0142a77651214f66f0a725253521"},{url:"/assets/images/icons/icon-512px.png",revision:"37175521e6de680e90740ead2506f9fd"},{url:"/assets/images/icons/icon-maskable-192px.png",revision:"291866775902df321181d8dbc66c0d22"},{url:"/assets/images/icons/icon-maskable-512px.png",revision:"d105aac16603bc9e5349fba31bf71cfd"},{url:"/assets/images/logo.svg",revision:"83079d38a7a118e1c80fe28d139991d8"},{url:"/assets/images/player-backgrounds/01.jpg",revision:"14d74db9eb062b39dc128daeba77cb63"},{url:"/assets/images/player-backgrounds/02.jpg",revision:"98e077363a5eec17da30acef5038f924"},{url:"/assets/images/player-backgrounds/03.jpg",revision:"e75e4fc34090286e347cebf12c74b1b8"},{url:"/assets/images/player-backgrounds/04.jpg",revision:"714dd3c050c09a16236f2424c548c83f"},{url:"/assets/images/player-backgrounds/05.jpg",revision:"717125c34121b326e8f90773565f59ca"},{url:"/assets/images/player-backgrounds/06.jpg",revision:"aa3b22785383baf67ad6d53fee94ed1c"},{url:"/assets/images/player-backgrounds/07.jpg",revision:"dc9937f7a374b99981cb0d6c9a642e56"},{url:"/assets/images/player-backgrounds/08.jpg",revision:"b6cedbf1da35814fbf784591380fde62"},{url:"/assets/images/player-backgrounds/09.jpg",revision:"e989450375d6954b37b066a1cec3ad35"},{url:"/assets/images/player-backgrounds/10.jpg",revision:"417128b6120078997139b44ee2c73dbd"},{url:"/assets/images/player-backgrounds/11.jpg",revision:"8c173e2d5980e09dc7b0e36e97b8f189"},{url:"/assets/images/player-backgrounds/12.jpg",revision:"97231a4813562229cc55d4516cb85350"},{url:"/assets/images/player-backgrounds/13.jpg",revision:"6efbebd72cadf7bdd59a0ad5325662d7"},{url:"/assets/images/player-backgrounds/14.jpg",revision:"54d47c83175ed7f11697a2cb3e54e3b1"},{url:"/assets/images/player-backgrounds/15.jpg",revision:"e9cb581540c06a770d299dede678ff0c"},{url:"/assets/images/player-backgrounds/16.jpg",revision:"b7e7ddc4ae9ba3811f3d5c0ae39a073f"},{url:"/assets/images/player-backgrounds/17.jpg",revision:"d363a4b8256115c7505a420ca6a55aae"},{url:"/assets/images/player-backgrounds/18.jpg",revision:"6c4e11b735bf6c95dfa5d47c3ae8e2e2"},{url:"/assets/images/player-backgrounds/19.jpg",revision:"7fdf1e54a13c7e9d34ceb170fb47c26a"},{url:"/assets/images/player-backgrounds/20.jpg",revision:"119ef99d06f809582244c2014ba005aa"},{url:"/assets/images/player-backgrounds/21.jpg",revision:"b83a101c3a856de1728790666e4c0040"},{url:"/assets/images/player-backgrounds/22.jpg",revision:"e6575b88d5aa774dc9b3c53e334c7c04"},{url:"/assets/images/player-backgrounds/23.jpg",revision:"c78d6d5548d8e2ed7b6681a6a29f75bb"},{url:"/assets/images/player-backgrounds/24.jpg",revision:"1da1420c684e6e51a0301b83544cf08c"},{url:"/assets/images/player-backgrounds/25.jpg",revision:"aa51d71045e5f5cc9d3b89daa344b917"},{url:"/assets/images/player-backgrounds/26.jpg",revision:"a8deb2d94eb69f1ccaaede00ba5bb6b7"},{url:"/assets/images/player-backgrounds/27.jpg",revision:"5dde7e046f56139835c5db0c397ea0bd"},{url:"/assets/images/player-backgrounds/28.jpg",revision:"a8027e60652ba8b43f436aba7895a82c"},{url:"/assets/images/player-backgrounds/29.jpg",revision:"e0b12e01312c0e627fb133726e44da8e"},{url:"/assets/images/player-backgrounds/30.jpg",revision:"b1841274afaa34e3f2c73a0bf6546c83"},{url:"/assets/images/player-backgrounds/31.jpg",revision:"89a95df22ca39eb75cf5be6ba869223e"},{url:"/assets/images/player-backgrounds/32.jpg",revision:"04aae5ba779d6f5637e40c9da4952e57"},{url:"/assets/images/player-backgrounds/33.jpg",revision:"cb273547824cbd6adbd3dc4a19e3741c"},{url:"/assets/images/player-backgrounds/34.jpg",revision:"772bdfc97346e0f4466db4f23aaa986f"},{url:"/assets/images/player-backgrounds/35.jpg",revision:"08635247d80c6eada10efd122a0233ae"},{url:"/assets/images/player-backgrounds/36.jpg",revision:"d941cbfec1db86258d3131c664c6c606"},{url:"/assets/images/player-backgrounds/37.jpg",revision:"4f79166b5886629699c8914996dae8ec"},{url:"/assets/images/player-backgrounds/38.jpg",revision:"428d6030a438a79fda9c3b891c49ab7e"},{url:"/assets/images/player-backgrounds/39.jpg",revision:"81a7227fee4c963573dbd748066e79e4"},{url:"/assets/images/player-backgrounds/40.jpg",revision:"e59c8f21613f90767eacfcf35a1827d2"},{url:"/assets/images/player-backgrounds/41.jpg",revision:"63aeaea733070316bff748bc113a4a46"},{url:"/assets/images/player-backgrounds/42.jpg",revision:"d1875f1d0349c573d3e8cb91f2aadc33"},{url:"/assets/images/player-backgrounds/43.jpg",revision:"e64ed14afdc88195682716c677108b80"},{url:"/assets/images/player-backgrounds/44.jpg",revision:"0c29f86f731632a6ac52703b68ffa274"},{url:"/assets/images/player-backgrounds/45.jpg",revision:"6b061167c6e71f473ee9adc45a7dbf7c"},{url:"/assets/images/player-backgrounds/46.jpg",revision:"d24c8fbd847cba6e0e99c85218daa430"},{url:"/assets/images/player-backgrounds/47.jpg",revision:"9a907a8ecadc4889f604b0a00564d1f9"},{url:"/assets/images/player-backgrounds/48.jpg",revision:"aaf24d179484067bf4bab284c52456dd"},{url:"/assets/images/player-backgrounds/49.jpg",revision:"0d6cffc1fe9c516400a904f8cea606b3"},{url:"/assets/images/player-backgrounds/50.jpg",revision:"dde138765e318791b3e236ab985f9e98"},{url:"/assets/js/app.238feba1.js",revision:null},{url:"/assets/js/chunk-vendors.d6487c76.js",revision:null},{url:"/assets/romsounds/01.wav",revision:"4187b218123ba4ff5de4e48ad3ee7778"},{url:"/assets/romsounds/02.wav",revision:"a6e40866a7da83a5a6a77c62686b2fa6"},{url:"/assets/romsounds/03.wav",revision:"30f5d254ec6c10bc37f0584e6cb2d0ed"},{url:"/assets/romsounds/04.wav",revision:"626bbd8f569576f18fba702740d731c5"},{url:"/assets/romsounds/05.wav",revision:"dbfbd7f4e2e7670f47dac6f52de3fd98"},{url:"/assets/romsounds/06.wav",revision:"5e68fa08d3621ab451a6daf1d52803b9"},{url:"/assets/romsounds/07.wav",revision:"b17f57be56bb2141660d2a18a497cf69"},{url:"/assets/romsounds/08.wav",revision:"88b1bed69315e657ecaa6e7cdaa032c5"},{url:"/assets/romsounds/09.wav",revision:"39dd16fc0f20240d5347448f9703e42a"},{url:"/assets/romsounds/10.wav",revision:"e0a34f995f013843fb5e552c2dc78a03"},{url:"/assets/romsounds/11.wav",revision:"4b6fd4f4bddcee2ad1987e4c82da9476"},{url:"/assets/romsounds/12.wav",revision:"e0e67a86607a7ad8457c4adefbac50e9"},{url:"/assets/romsounds/13.wav",revision:"d6c8ef577228462c7b90e677396ca652"},{url:"/assets/romsounds/14.wav",revision:"d72e6dd844260adc6db71a04d3763d07"},{url:"/index.html",revision:"42c665a70c24aaf56c2bbfe907e45c09"},{url:"/manifest.json",revision:"c1a7314b537716c7416fe56300a68a54"},{url:"/robots.txt",revision:"d98e9ec5fafad691048cbb603b5b0e71"}],{}),s.cleanupOutdatedCaches()})); //# sourceMappingURL=service-worker.js.map diff --git a/client/dist/service-worker.js.map b/client/dist/service-worker.js.map index 5714c0c7..d728e51c 100644 --- a/client/dist/service-worker.js.map +++ b/client/dist/service-worker.js.map @@ -1 +1 @@ -{"version":3,"file":"service-worker.js","sources":["../../../../../../tmp/0171d0aebc36c6104d8c91d52abe6561/service-worker.js"],"sourcesContent":["import {setCacheNameDetails as workbox_core_setCacheNameDetails} from '/home/runner/work/KonomiTV/KonomiTV/client/node_modules/workbox-core/setCacheNameDetails.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/home/runner/work/KonomiTV/KonomiTV/client/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {cleanupOutdatedCaches as workbox_precaching_cleanupOutdatedCaches} from '/home/runner/work/KonomiTV/KonomiTV/client/node_modules/workbox-precaching/cleanupOutdatedCaches.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\n\nworkbox_core_setCacheNameDetails({prefix: \"KonomiTV\"});\n\n\nself.addEventListener('message', (event) => {\n if (event.data && event.data.type === 'SKIP_WAITING') {\n self.skipWaiting();\n }\n});\n\n\n\n\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"/assets/css/app.de64fc4c.css\",\n \"revision\": null\n },\n {\n \"url\": \"/assets/css/chunk-vendors.1de61f0e.css\",\n \"revision\": null\n },\n {\n \"url\": \"/assets/images/account-icon-default.png\",\n \"revision\": \"3840f879e0ddf77549f4035ae72e8f6b\"\n },\n {\n \"url\": \"/assets/images/icon.svg\",\n \"revision\": \"63abc49a99bd463af26e73cec607771d\"\n },\n {\n \"url\": \"/assets/images/icons/apple-touch-icon.png\",\n \"revision\": \"a1ff224fdbecfd10c117cd6172799b94\"\n },\n {\n \"url\": \"/assets/images/icons/favicon-16px.png\",\n \"revision\": \"66d1179e73198777a49235a76619a093\"\n },\n {\n \"url\": \"/assets/images/icons/favicon-32px.png\",\n \"revision\": \"85e6e77bb3362197cf564bf9b21ebe12\"\n },\n {\n \"url\": \"/assets/images/icons/favicon.svg\",\n \"revision\": \"1bf40917c217fd567119c219ebabe4b9\"\n },\n {\n \"url\": \"/assets/images/icons/icon-192px.png\",\n \"revision\": \"cc3f0142a77651214f66f0a725253521\"\n },\n {\n \"url\": \"/assets/images/icons/icon-512px.png\",\n \"revision\": \"37175521e6de680e90740ead2506f9fd\"\n },\n {\n \"url\": \"/assets/images/icons/icon-maskable-192px.png\",\n \"revision\": \"291866775902df321181d8dbc66c0d22\"\n },\n {\n \"url\": \"/assets/images/icons/icon-maskable-512px.png\",\n \"revision\": \"d105aac16603bc9e5349fba31bf71cfd\"\n },\n {\n \"url\": \"/assets/images/logo.svg\",\n \"revision\": \"83079d38a7a118e1c80fe28d139991d8\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/01.jpg\",\n \"revision\": \"14d74db9eb062b39dc128daeba77cb63\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/02.jpg\",\n \"revision\": \"98e077363a5eec17da30acef5038f924\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/03.jpg\",\n \"revision\": \"e75e4fc34090286e347cebf12c74b1b8\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/04.jpg\",\n \"revision\": \"714dd3c050c09a16236f2424c548c83f\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/05.jpg\",\n \"revision\": \"717125c34121b326e8f90773565f59ca\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/06.jpg\",\n \"revision\": \"aa3b22785383baf67ad6d53fee94ed1c\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/07.jpg\",\n \"revision\": \"dc9937f7a374b99981cb0d6c9a642e56\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/08.jpg\",\n \"revision\": \"b6cedbf1da35814fbf784591380fde62\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/09.jpg\",\n \"revision\": \"e989450375d6954b37b066a1cec3ad35\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/10.jpg\",\n \"revision\": \"417128b6120078997139b44ee2c73dbd\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/11.jpg\",\n \"revision\": \"8c173e2d5980e09dc7b0e36e97b8f189\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/12.jpg\",\n \"revision\": \"97231a4813562229cc55d4516cb85350\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/13.jpg\",\n \"revision\": \"6efbebd72cadf7bdd59a0ad5325662d7\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/14.jpg\",\n \"revision\": \"54d47c83175ed7f11697a2cb3e54e3b1\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/15.jpg\",\n \"revision\": \"e9cb581540c06a770d299dede678ff0c\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/16.jpg\",\n \"revision\": \"b7e7ddc4ae9ba3811f3d5c0ae39a073f\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/17.jpg\",\n \"revision\": \"d363a4b8256115c7505a420ca6a55aae\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/18.jpg\",\n \"revision\": \"6c4e11b735bf6c95dfa5d47c3ae8e2e2\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/19.jpg\",\n \"revision\": \"7fdf1e54a13c7e9d34ceb170fb47c26a\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/20.jpg\",\n \"revision\": \"119ef99d06f809582244c2014ba005aa\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/21.jpg\",\n \"revision\": \"b83a101c3a856de1728790666e4c0040\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/22.jpg\",\n \"revision\": \"e6575b88d5aa774dc9b3c53e334c7c04\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/23.jpg\",\n \"revision\": \"c78d6d5548d8e2ed7b6681a6a29f75bb\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/24.jpg\",\n \"revision\": \"1da1420c684e6e51a0301b83544cf08c\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/25.jpg\",\n \"revision\": \"aa51d71045e5f5cc9d3b89daa344b917\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/26.jpg\",\n \"revision\": \"a8deb2d94eb69f1ccaaede00ba5bb6b7\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/27.jpg\",\n \"revision\": \"5dde7e046f56139835c5db0c397ea0bd\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/28.jpg\",\n \"revision\": \"a8027e60652ba8b43f436aba7895a82c\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/29.jpg\",\n \"revision\": \"e0b12e01312c0e627fb133726e44da8e\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/30.jpg\",\n \"revision\": \"b1841274afaa34e3f2c73a0bf6546c83\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/31.jpg\",\n \"revision\": \"89a95df22ca39eb75cf5be6ba869223e\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/32.jpg\",\n \"revision\": \"04aae5ba779d6f5637e40c9da4952e57\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/33.jpg\",\n \"revision\": \"cb273547824cbd6adbd3dc4a19e3741c\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/34.jpg\",\n \"revision\": \"772bdfc97346e0f4466db4f23aaa986f\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/35.jpg\",\n \"revision\": \"08635247d80c6eada10efd122a0233ae\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/36.jpg\",\n \"revision\": \"d941cbfec1db86258d3131c664c6c606\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/37.jpg\",\n \"revision\": \"4f79166b5886629699c8914996dae8ec\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/38.jpg\",\n \"revision\": \"428d6030a438a79fda9c3b891c49ab7e\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/39.jpg\",\n \"revision\": \"81a7227fee4c963573dbd748066e79e4\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/40.jpg\",\n \"revision\": \"e59c8f21613f90767eacfcf35a1827d2\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/41.jpg\",\n \"revision\": \"63aeaea733070316bff748bc113a4a46\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/42.jpg\",\n \"revision\": \"d1875f1d0349c573d3e8cb91f2aadc33\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/43.jpg\",\n \"revision\": \"e64ed14afdc88195682716c677108b80\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/44.jpg\",\n \"revision\": \"0c29f86f731632a6ac52703b68ffa274\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/45.jpg\",\n \"revision\": \"6b061167c6e71f473ee9adc45a7dbf7c\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/46.jpg\",\n \"revision\": \"d24c8fbd847cba6e0e99c85218daa430\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/47.jpg\",\n \"revision\": \"9a907a8ecadc4889f604b0a00564d1f9\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/48.jpg\",\n \"revision\": \"aaf24d179484067bf4bab284c52456dd\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/49.jpg\",\n \"revision\": \"0d6cffc1fe9c516400a904f8cea606b3\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/50.jpg\",\n \"revision\": \"dde138765e318791b3e236ab985f9e98\"\n },\n {\n \"url\": \"/assets/js/app.8188c78a.js\",\n \"revision\": null\n },\n {\n \"url\": \"/assets/js/chunk-vendors.d6487c76.js\",\n \"revision\": null\n },\n {\n \"url\": \"/assets/romsounds/01.wav\",\n \"revision\": \"4187b218123ba4ff5de4e48ad3ee7778\"\n },\n {\n \"url\": \"/assets/romsounds/02.wav\",\n \"revision\": \"a6e40866a7da83a5a6a77c62686b2fa6\"\n },\n {\n \"url\": \"/assets/romsounds/03.wav\",\n \"revision\": \"30f5d254ec6c10bc37f0584e6cb2d0ed\"\n },\n {\n \"url\": \"/assets/romsounds/04.wav\",\n \"revision\": \"626bbd8f569576f18fba702740d731c5\"\n },\n {\n \"url\": \"/assets/romsounds/05.wav\",\n \"revision\": \"dbfbd7f4e2e7670f47dac6f52de3fd98\"\n },\n {\n \"url\": \"/assets/romsounds/06.wav\",\n \"revision\": \"5e68fa08d3621ab451a6daf1d52803b9\"\n },\n {\n \"url\": \"/assets/romsounds/07.wav\",\n \"revision\": \"b17f57be56bb2141660d2a18a497cf69\"\n },\n {\n \"url\": \"/assets/romsounds/08.wav\",\n \"revision\": \"88b1bed69315e657ecaa6e7cdaa032c5\"\n },\n {\n \"url\": \"/assets/romsounds/09.wav\",\n \"revision\": \"39dd16fc0f20240d5347448f9703e42a\"\n },\n {\n \"url\": \"/assets/romsounds/10.wav\",\n \"revision\": \"e0a34f995f013843fb5e552c2dc78a03\"\n },\n {\n \"url\": \"/assets/romsounds/11.wav\",\n \"revision\": \"4b6fd4f4bddcee2ad1987e4c82da9476\"\n },\n {\n \"url\": \"/assets/romsounds/12.wav\",\n \"revision\": \"e0e67a86607a7ad8457c4adefbac50e9\"\n },\n {\n \"url\": \"/assets/romsounds/13.wav\",\n \"revision\": \"d6c8ef577228462c7b90e677396ca652\"\n },\n {\n \"url\": \"/assets/romsounds/14.wav\",\n \"revision\": \"d72e6dd844260adc6db71a04d3763d07\"\n },\n {\n \"url\": \"/index.html\",\n \"revision\": \"acfeb175ea1525d95d2caeec4389fda9\"\n },\n {\n \"url\": \"/manifest.json\",\n \"revision\": \"c1a7314b537716c7416fe56300a68a54\"\n },\n {\n \"url\": \"/robots.txt\",\n \"revision\": \"d98e9ec5fafad691048cbb603b5b0e71\"\n }\n], {});\nworkbox_precaching_cleanupOutdatedCaches();\n\n\n\n\n\n\n\n"],"names":["workbox_core_setCacheNameDetails","prefix","self","addEventListener","event","data","type","skipWaiting","workbox_precaching_precacheAndRoute","url","revision","workbox_precaching_cleanupOutdatedCaches"],"mappings":"0nBAkBAA,EAAAA,oBAAiC,CAACC,OAAQ,aAG1CC,KAAKC,iBAAiB,WAAYC,IAC5BA,EAAMC,MAA4B,iBAApBD,EAAMC,KAAKC,MAC3BJ,KAAKK,aACP,IAWFC,EAAAA,iBAAoC,CAClC,CACEC,IAAO,+BACPC,SAAY,MAEd,CACED,IAAO,yCACPC,SAAY,MAEd,CACED,IAAO,0CACPC,SAAY,oCAEd,CACED,IAAO,0BACPC,SAAY,oCAEd,CACED,IAAO,4CACPC,SAAY,oCAEd,CACED,IAAO,wCACPC,SAAY,oCAEd,CACED,IAAO,wCACPC,SAAY,oCAEd,CACED,IAAO,mCACPC,SAAY,oCAEd,CACED,IAAO,sCACPC,SAAY,oCAEd,CACED,IAAO,sCACPC,SAAY,oCAEd,CACED,IAAO,+CACPC,SAAY,oCAEd,CACED,IAAO,+CACPC,SAAY,oCAEd,CACED,IAAO,0BACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,MAEd,CACED,IAAO,uCACPC,SAAY,MAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,cACPC,SAAY,oCAEd,CACED,IAAO,iBACPC,SAAY,oCAEd,CACED,IAAO,cACPC,SAAY,qCAEb,CAAE,GACLC,EAAAA"} \ No newline at end of file +{"version":3,"file":"service-worker.js","sources":["../../../../../../tmp/1751489242712b57da06dcb80b96960b/service-worker.js"],"sourcesContent":["import {setCacheNameDetails as workbox_core_setCacheNameDetails} from '/home/runner/work/KonomiTV/KonomiTV/client/node_modules/workbox-core/setCacheNameDetails.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/home/runner/work/KonomiTV/KonomiTV/client/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {cleanupOutdatedCaches as workbox_precaching_cleanupOutdatedCaches} from '/home/runner/work/KonomiTV/KonomiTV/client/node_modules/workbox-precaching/cleanupOutdatedCaches.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\n\nworkbox_core_setCacheNameDetails({prefix: \"KonomiTV\"});\n\n\nself.addEventListener('message', (event) => {\n if (event.data && event.data.type === 'SKIP_WAITING') {\n self.skipWaiting();\n }\n});\n\n\n\n\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"/assets/css/app.114f8e66.css\",\n \"revision\": null\n },\n {\n \"url\": \"/assets/css/chunk-vendors.1de61f0e.css\",\n \"revision\": null\n },\n {\n \"url\": \"/assets/images/account-icon-default.png\",\n \"revision\": \"3840f879e0ddf77549f4035ae72e8f6b\"\n },\n {\n \"url\": \"/assets/images/icon.svg\",\n \"revision\": \"63abc49a99bd463af26e73cec607771d\"\n },\n {\n \"url\": \"/assets/images/icons/apple-touch-icon.png\",\n \"revision\": \"a1ff224fdbecfd10c117cd6172799b94\"\n },\n {\n \"url\": \"/assets/images/icons/favicon-16px.png\",\n \"revision\": \"66d1179e73198777a49235a76619a093\"\n },\n {\n \"url\": \"/assets/images/icons/favicon-32px.png\",\n \"revision\": \"85e6e77bb3362197cf564bf9b21ebe12\"\n },\n {\n \"url\": \"/assets/images/icons/favicon.svg\",\n \"revision\": \"1bf40917c217fd567119c219ebabe4b9\"\n },\n {\n \"url\": \"/assets/images/icons/icon-192px.png\",\n \"revision\": \"cc3f0142a77651214f66f0a725253521\"\n },\n {\n \"url\": \"/assets/images/icons/icon-512px.png\",\n \"revision\": \"37175521e6de680e90740ead2506f9fd\"\n },\n {\n \"url\": \"/assets/images/icons/icon-maskable-192px.png\",\n \"revision\": \"291866775902df321181d8dbc66c0d22\"\n },\n {\n \"url\": \"/assets/images/icons/icon-maskable-512px.png\",\n \"revision\": \"d105aac16603bc9e5349fba31bf71cfd\"\n },\n {\n \"url\": \"/assets/images/logo.svg\",\n \"revision\": \"83079d38a7a118e1c80fe28d139991d8\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/01.jpg\",\n \"revision\": \"14d74db9eb062b39dc128daeba77cb63\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/02.jpg\",\n \"revision\": \"98e077363a5eec17da30acef5038f924\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/03.jpg\",\n \"revision\": \"e75e4fc34090286e347cebf12c74b1b8\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/04.jpg\",\n \"revision\": \"714dd3c050c09a16236f2424c548c83f\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/05.jpg\",\n \"revision\": \"717125c34121b326e8f90773565f59ca\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/06.jpg\",\n \"revision\": \"aa3b22785383baf67ad6d53fee94ed1c\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/07.jpg\",\n \"revision\": \"dc9937f7a374b99981cb0d6c9a642e56\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/08.jpg\",\n \"revision\": \"b6cedbf1da35814fbf784591380fde62\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/09.jpg\",\n \"revision\": \"e989450375d6954b37b066a1cec3ad35\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/10.jpg\",\n \"revision\": \"417128b6120078997139b44ee2c73dbd\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/11.jpg\",\n \"revision\": \"8c173e2d5980e09dc7b0e36e97b8f189\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/12.jpg\",\n \"revision\": \"97231a4813562229cc55d4516cb85350\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/13.jpg\",\n \"revision\": \"6efbebd72cadf7bdd59a0ad5325662d7\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/14.jpg\",\n \"revision\": \"54d47c83175ed7f11697a2cb3e54e3b1\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/15.jpg\",\n \"revision\": \"e9cb581540c06a770d299dede678ff0c\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/16.jpg\",\n \"revision\": \"b7e7ddc4ae9ba3811f3d5c0ae39a073f\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/17.jpg\",\n \"revision\": \"d363a4b8256115c7505a420ca6a55aae\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/18.jpg\",\n \"revision\": \"6c4e11b735bf6c95dfa5d47c3ae8e2e2\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/19.jpg\",\n \"revision\": \"7fdf1e54a13c7e9d34ceb170fb47c26a\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/20.jpg\",\n \"revision\": \"119ef99d06f809582244c2014ba005aa\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/21.jpg\",\n \"revision\": \"b83a101c3a856de1728790666e4c0040\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/22.jpg\",\n \"revision\": \"e6575b88d5aa774dc9b3c53e334c7c04\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/23.jpg\",\n \"revision\": \"c78d6d5548d8e2ed7b6681a6a29f75bb\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/24.jpg\",\n \"revision\": \"1da1420c684e6e51a0301b83544cf08c\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/25.jpg\",\n \"revision\": \"aa51d71045e5f5cc9d3b89daa344b917\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/26.jpg\",\n \"revision\": \"a8deb2d94eb69f1ccaaede00ba5bb6b7\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/27.jpg\",\n \"revision\": \"5dde7e046f56139835c5db0c397ea0bd\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/28.jpg\",\n \"revision\": \"a8027e60652ba8b43f436aba7895a82c\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/29.jpg\",\n \"revision\": \"e0b12e01312c0e627fb133726e44da8e\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/30.jpg\",\n \"revision\": \"b1841274afaa34e3f2c73a0bf6546c83\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/31.jpg\",\n \"revision\": \"89a95df22ca39eb75cf5be6ba869223e\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/32.jpg\",\n \"revision\": \"04aae5ba779d6f5637e40c9da4952e57\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/33.jpg\",\n \"revision\": \"cb273547824cbd6adbd3dc4a19e3741c\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/34.jpg\",\n \"revision\": \"772bdfc97346e0f4466db4f23aaa986f\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/35.jpg\",\n \"revision\": \"08635247d80c6eada10efd122a0233ae\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/36.jpg\",\n \"revision\": \"d941cbfec1db86258d3131c664c6c606\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/37.jpg\",\n \"revision\": \"4f79166b5886629699c8914996dae8ec\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/38.jpg\",\n \"revision\": \"428d6030a438a79fda9c3b891c49ab7e\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/39.jpg\",\n \"revision\": \"81a7227fee4c963573dbd748066e79e4\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/40.jpg\",\n \"revision\": \"e59c8f21613f90767eacfcf35a1827d2\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/41.jpg\",\n \"revision\": \"63aeaea733070316bff748bc113a4a46\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/42.jpg\",\n \"revision\": \"d1875f1d0349c573d3e8cb91f2aadc33\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/43.jpg\",\n \"revision\": \"e64ed14afdc88195682716c677108b80\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/44.jpg\",\n \"revision\": \"0c29f86f731632a6ac52703b68ffa274\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/45.jpg\",\n \"revision\": \"6b061167c6e71f473ee9adc45a7dbf7c\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/46.jpg\",\n \"revision\": \"d24c8fbd847cba6e0e99c85218daa430\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/47.jpg\",\n \"revision\": \"9a907a8ecadc4889f604b0a00564d1f9\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/48.jpg\",\n \"revision\": \"aaf24d179484067bf4bab284c52456dd\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/49.jpg\",\n \"revision\": \"0d6cffc1fe9c516400a904f8cea606b3\"\n },\n {\n \"url\": \"/assets/images/player-backgrounds/50.jpg\",\n \"revision\": \"dde138765e318791b3e236ab985f9e98\"\n },\n {\n \"url\": \"/assets/js/app.238feba1.js\",\n \"revision\": null\n },\n {\n \"url\": \"/assets/js/chunk-vendors.d6487c76.js\",\n \"revision\": null\n },\n {\n \"url\": \"/assets/romsounds/01.wav\",\n \"revision\": \"4187b218123ba4ff5de4e48ad3ee7778\"\n },\n {\n \"url\": \"/assets/romsounds/02.wav\",\n \"revision\": \"a6e40866a7da83a5a6a77c62686b2fa6\"\n },\n {\n \"url\": \"/assets/romsounds/03.wav\",\n \"revision\": \"30f5d254ec6c10bc37f0584e6cb2d0ed\"\n },\n {\n \"url\": \"/assets/romsounds/04.wav\",\n \"revision\": \"626bbd8f569576f18fba702740d731c5\"\n },\n {\n \"url\": \"/assets/romsounds/05.wav\",\n \"revision\": \"dbfbd7f4e2e7670f47dac6f52de3fd98\"\n },\n {\n \"url\": \"/assets/romsounds/06.wav\",\n \"revision\": \"5e68fa08d3621ab451a6daf1d52803b9\"\n },\n {\n \"url\": \"/assets/romsounds/07.wav\",\n \"revision\": \"b17f57be56bb2141660d2a18a497cf69\"\n },\n {\n \"url\": \"/assets/romsounds/08.wav\",\n \"revision\": \"88b1bed69315e657ecaa6e7cdaa032c5\"\n },\n {\n \"url\": \"/assets/romsounds/09.wav\",\n \"revision\": \"39dd16fc0f20240d5347448f9703e42a\"\n },\n {\n \"url\": \"/assets/romsounds/10.wav\",\n \"revision\": \"e0a34f995f013843fb5e552c2dc78a03\"\n },\n {\n \"url\": \"/assets/romsounds/11.wav\",\n \"revision\": \"4b6fd4f4bddcee2ad1987e4c82da9476\"\n },\n {\n \"url\": \"/assets/romsounds/12.wav\",\n \"revision\": \"e0e67a86607a7ad8457c4adefbac50e9\"\n },\n {\n \"url\": \"/assets/romsounds/13.wav\",\n \"revision\": \"d6c8ef577228462c7b90e677396ca652\"\n },\n {\n \"url\": \"/assets/romsounds/14.wav\",\n \"revision\": \"d72e6dd844260adc6db71a04d3763d07\"\n },\n {\n \"url\": \"/index.html\",\n \"revision\": \"42c665a70c24aaf56c2bbfe907e45c09\"\n },\n {\n \"url\": \"/manifest.json\",\n \"revision\": \"c1a7314b537716c7416fe56300a68a54\"\n },\n {\n \"url\": \"/robots.txt\",\n \"revision\": \"d98e9ec5fafad691048cbb603b5b0e71\"\n }\n], {});\nworkbox_precaching_cleanupOutdatedCaches();\n\n\n\n\n\n\n\n"],"names":["workbox_core_setCacheNameDetails","prefix","self","addEventListener","event","data","type","skipWaiting","workbox_precaching_precacheAndRoute","url","revision","workbox_precaching_cleanupOutdatedCaches"],"mappings":"0nBAkBAA,EAAAA,oBAAiC,CAACC,OAAQ,aAG1CC,KAAKC,iBAAiB,WAAYC,IAC5BA,EAAMC,MAA4B,iBAApBD,EAAMC,KAAKC,MAC3BJ,KAAKK,aACP,IAWFC,EAAAA,iBAAoC,CAClC,CACEC,IAAO,+BACPC,SAAY,MAEd,CACED,IAAO,yCACPC,SAAY,MAEd,CACED,IAAO,0CACPC,SAAY,oCAEd,CACED,IAAO,0BACPC,SAAY,oCAEd,CACED,IAAO,4CACPC,SAAY,oCAEd,CACED,IAAO,wCACPC,SAAY,oCAEd,CACED,IAAO,wCACPC,SAAY,oCAEd,CACED,IAAO,mCACPC,SAAY,oCAEd,CACED,IAAO,sCACPC,SAAY,oCAEd,CACED,IAAO,sCACPC,SAAY,oCAEd,CACED,IAAO,+CACPC,SAAY,oCAEd,CACED,IAAO,+CACPC,SAAY,oCAEd,CACED,IAAO,0BACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,2CACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,MAEd,CACED,IAAO,uCACPC,SAAY,MAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,2BACPC,SAAY,oCAEd,CACED,IAAO,cACPC,SAAY,oCAEd,CACED,IAAO,iBACPC,SAAY,oCAEd,CACED,IAAO,cACPC,SAAY,qCAEb,CAAE,GACLC,EAAAA"} \ No newline at end of file diff --git a/client/package.json b/client/package.json index 1742dbb7..717dcaaf 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "KonomiTV", - "version": "0.7.0", + "version": "0.7.1", "private": true, "scripts": { "dev": "concurrently --raw \"npm:dev-https\" \"vue-cli-service serve\"", diff --git a/installer/KonomiTV-Installer.py b/installer/KonomiTV-Installer.py index f55d890d..27fca8dd 100644 --- a/installer/KonomiTV-Installer.py +++ b/installer/KonomiTV-Installer.py @@ -24,7 +24,7 @@ # インストール or アップデート対象の KonomiTV バージョン -TARGET_VERSION = '0.7.0' +TARGET_VERSION = '0.7.1' def ShowHeader(): print(Padding(Rule( diff --git a/server/app/constants.py b/server/app/constants.py index 8decde23..0574f3a5 100644 --- a/server/app/constants.py +++ b/server/app/constants.py @@ -12,7 +12,7 @@ # バージョン -VERSION = '0.7.0' +VERSION = '0.7.1' # ベースディレクトリ BASE_DIR = Path(__file__).resolve().parent.parent