Tarik untuk refresh

Memuat...

0; var canB2 = !!(d.stream_urls && d.stream_urls.length); var canGd = hasGd; var canHls = !!d.hls_url; this._prevServer = this.server; // Fullscreen bisa native (document.fullscreenElement) ATAU // mode fallback CSS Plyr (`.plyr--fullscreen-fallback`, dipakai // WebView / browser tanpa Fullscreen API). Kalau hanya cek // document.fullscreenElement, di mode fallback ini nilainya // null -> re-enter tak pernah jalan -> keluar fullscreen. this._wasFullscreen = !!(document.fullscreenElement || document.querySelector('.plyr--fullscreen-fallback')); const c = document.getElementById('anime-player-container'); if (c) { var spriteUrl = d.sprite_url ? d.sprite_url : null; window.b2PlayerOptions = { sources: d.stream_urls, gdriveSources: gdSrcs, episodeId: d.id, prevUrl: d.prev_url, nextUrl: d.next_url, skipIntroAt: null, posterUrl: d.poster_url, title: epTitle, slug: slug, currentEp: d.episode_number, episodes: window.b2PlayerOptions?.episodes || [], epTotal: window.b2PlayerOptions?.epTotal || 0, epPerPage: window.b2PlayerOptions?.epPerPage || 50, epCurrentPage: window.b2PlayerOptions?.epCurrentPage || 1, logoUrl: window.b2PlayerOptions?.logoUrl || null, spriteUrl: spriteUrl, }; var _pp = window.b2PlayerOptions.epPerPage || 52; var _eps = window.b2PlayerOptions.episodes || []; var _epIdx = _eps.findIndex(function(e) { return e.id === d.id; }); window.b2PlayerOptions.epCurrentPage = _epIdx >= 0 ? Math.floor(_epIdx / _pp) + 1 : 1; } this.gdriveSources = gdSrcs; if (window.b2PlayerOptions) window.b2PlayerOptions.hlsUrl = d.hls_url || null; // Resolve the target server BEFORE touching any player, otherwise the // previous episode keeps playing inside a display:none frame (double audio). var order = (this.server === 'hls') ? ['hls', 'b2', 'gdrive'] : ['b2', 'gdrive', 'hls']; var picked = null; for (var oi = 0; oi < order.length; oi++) { if (order[oi] === 'b2' && canB2) { picked = 'b2'; break; } if (order[oi] === 'gdrive' && canGd) { picked = 'gdrive'; break; } if (order[oi] === 'hls' && canHls) { picked = 'hls'; break; } } if (!picked) picked = canB2 ? 'b2' : (canGd ? 'gdrive' : (canHls ? 'hls' : this.server)); this.server = picked; this.pauseInactivePlayers(); // Player langsung mencoba primary GDrive. Kalau kena 403, // player.js (_handleFailure) yang menampilkan overlay dan // memicu mirror on-demand — jadi pindah episode tidak lagi // tertahan menunggu mirror dibuat. if (picked === 'hls' || (!canB2 && !canGd)) { this.killDirectPlayer(); } else if (c) { var gdNow = (picked === 'gdrive'); var srces = gdNow ? gdSrcs : d.stream_urls; if (gdNow) this.gdriveLoaded = false; else this.b2Loaded = false; if (c._player && c._player.switchSource) { c._player.isGdrive = gdNow; c._player.switchSource( srces, d.prev_url, d.next_url, d.poster_url, epTitle, slug, window.b2PlayerOptions.episodes, d.episode_number, window.b2PlayerOptions.epCurrentPage, window.b2PlayerOptions.epPerPage, window.b2PlayerOptions.epTotal, d.id, spriteUrl ); const v = c.querySelector('#anime-video'); if (v) { let _done = false; const _ready = () => { if (_done) return; _done = true; if (gdNow) this.gdriveLoaded = true; else this.b2Loaded = true; }; v.addEventListener('playing', _ready, { once: true }); v.addEventListener('canplay', _ready, { once: true }); setTimeout(_ready, 8000); } else { if (gdNow) this.gdriveLoaded = true; else this.b2Loaded = true; } } else { c.querySelectorAll('video').forEach((v) => this._hardStopMedia(v)); c.innerHTML = ''; this.$nextTick(() => { if (gdNow) window.initAnimePlayer(c, Object.assign({}, window.b2PlayerOptions, { sources: srces, isGdrive: true })); else window.initAnimePlayer(c, window.b2PlayerOptions); if (gdNow) this.gdriveLoaded = true; else this.b2Loaded = true; }); } } // HLS player: (re)source it only after the server is resolved. if (this.server === 'hls') { this.hlsLoaded = false; const hc = document.getElementById('hls-player-container'); // A new episode is an explicit play intent -- unless the // user paused while this switch was still in flight. if (hc && (hc._hlsPauseEpoch || 0) === hcEp0) hc._hlsUserPaused = false; if (!canHls) { this.killHlsPlayer(); } else if (hc && typeof hc.switchHlsSource === 'function') { hc.switchHlsSource(d.hls_url); this.hlsLoaded = true; } else { this.$nextTick(() => this.loadHls()); } } else { this._pauseHls(); } // 1. Release date if (this.$refs.epReleaseDate) { var dt = this.$refs.epReleaseDate; if (d.formatted_release_date) { dt.querySelector('span') ? dt.querySelector('span').textContent = d.formatted_release_date : dt.appendChild(document.createTextNode(d.formatted_release_date)); dt.style.display = ''; } else { dt.style.display = 'none'; } } // 3. Duration if (this.$refs.epDuration) { if (d.duration) { this.$refs.epDuration.querySelector('span').textContent = d.duration; this.$refs.epDuration.style.display = ''; } else { this.$refs.epDuration.style.display = 'none'; } } // 4. Description if (this.$refs.epDescription) { var descP = this.$refs.epDescription.querySelector('p'); if (descP) { if (d.description) { descP.innerHTML = 'Episode ' + epLabel + ' - ' + d.description; this.$refs.epDescription.style.display = ''; } else { this.$refs.epDescription.style.display = 'none'; } } } // 5. Like button var likeBtn = document.getElementById('like-btn'); if (likeBtn) { likeBtn.dataset.episodeId = d.id; likeBtn.dataset.liked = d.is_liked ? '1' : '0'; var lc = document.getElementById('like-count'); if (lc) lc.textContent = d.likes_count || '0'; var icon = likeBtn.querySelector('svg'); if (d.is_liked) { likeBtn.classList.add('liked'); if (icon) icon.setAttribute('fill', 'currentColor'); } else { likeBtn.classList.remove('liked'); if (icon) icon.setAttribute('fill', 'none'); } } // 7. Update current episode ID for pagination this.currentEpisodeId = d.id; // 9. HLS / GDrive / B2 tabs — source + player already resolved above this.hasHls = canHls; this.hasGdrive = canGd; this.hasB2Stream = canB2; if (this._wasFullscreen && this._prevServer !== this.server) { const target = this._isDirect() ? document.getElementById('anime-player-container') : document.getElementById('hls-player-container'); this._reenterFullscreen(target, 0); } var posterOverlays = document.querySelectorAll('.watch-player-frame [style*="background-image"]'); if (d.poster_url) { for (var i = 0; i < posterOverlays.length; i++) { posterOverlays[i].style.backgroundImage = 'url(\'' + d.poster_url + '\')'; } } // 10. Prev/Next buttons if (this.$refs.prevBtn) { if (d.prev_url) { this.$refs.prevBtn.href = d.prev_url; this.$refs.prevBtn.classList.remove('hidden'); } else { this.$refs.prevBtn.classList.add('hidden'); } } if (this.$refs.nextBtn) { if (d.next_url) { this.$refs.nextBtn.href = d.next_url; this.$refs.nextBtn.classList.remove('hidden'); } else { this.$refs.nextBtn.classList.add('hidden'); } } // 11. Browser URL history.pushState(null, '', '/anime/' + slug + '/episode/' + d.episode_number); // 12. Breadcrumb episode var breadcrumbEpisode = document.querySelector('#watch-breadcrumb .crumb-seg:last-child .crumb-label'); if (breadcrumbEpisode) { breadcrumbEpisode.textContent = false ? 'Movie' : 'Ep ' + epLabel; } // 13. Episode chips active state document.querySelectorAll('.ep-chip').forEach(function(el) { el.classList.remove('active'); if (parseInt(el.dataset.ep) === d.episode_number) { el.classList.add('active'); } }); // 12b. Full grid items active state (showAllEpisodes) document.querySelectorAll('.ep-btn, .ep-btn-sidebar').forEach(function(el) { if (parseInt(el.dataset.ep) === d.episode_number) { el.classList.add('bg-purple-600', 'text-white'); el.classList.remove('bg-gray-800', 'text-gray-400', 'border', 'border-gray-700/50', 'hover:bg-gray-700', 'bg-gray-700/50'); } else { el.classList.remove('bg-purple-600', 'text-white'); el.classList.add('bg-gray-800', 'text-gray-400', 'border', 'border-gray-700/50', 'hover:bg-gray-700'); } }); // 13. Sidebar episodes active state document.querySelectorAll('.sidebar-ep-item').forEach(function(el) { el.classList.remove('active', 'bg-purple-600/10'); el.classList.add('hover:bg-white/5'); if (parseInt(el.dataset.ep) === d.episode_number) { el.classList.remove('hover:bg-white/5'); el.classList.add('active', 'bg-purple-600/10'); } }); // 14. Repost button var rpBtn = document.getElementById('repost-btn'); if (rpBtn) rpBtn.dataset.episodeId = d.id; // 15. Download link var dlLink = document.getElementById('download-link'); if (dlLink) dlLink.href = '/anime/' + slug + '/episode/' + d.episode_number + '/download'; // 16. Report forms document.querySelectorAll('.report-form').forEach(function(f) { f.action = '/episode/' + d.id + '/report'; }); // 17. Comments if (d.comments_html) { var csEl = document.getElementById('comment-section'); if (csEl) csEl.outerHTML = d.comments_html; } // 18. Episode Berikutnya var nexEl = document.getElementById('next-episode-container'); if (nexEl) { nexEl.innerHTML = d.next_episode_html || ''; nexEl.hidden = !(d.next_episode_html || '').trim(); } // Safety net: only the resolved server may keep playing. this.pauseInactivePlayers(); this._loading = false; }) .catch(() => { this._loading = false; }); }, // Keluar dari fullscreen (native maupun fallback CSS) pada player // selain `keep`. _exitOtherFullscreens(keep) { const keepPlayer = keep ? (keep._player ? keep._player.player : keep._hlsPlayer) : null; const keepTarget = keepPlayer && keepPlayer.fullscreen ? keepPlayer.fullscreen.target : null; ['anime-player-container', 'hls-player-container'].forEach((id) => { const oc = document.getElementById(id); if (!oc || oc === keep) return; const op = oc._player ? oc._player.player : oc._hlsPlayer; try { if (!op || !op.fullscreen || !op.fullscreen.active) return; // Target fullscreen sama (`.player-fs-shell` dipakai bersama // kedua player) -> JANGAN di-exit; itu justru menutup // fullscreen yang sedang aktif. if (keepTarget && op.fullscreen.target === keepTarget) return; op.fullscreen.exit(); } catch (e) {} }); }, _reenterFullscreen(container, attempts) { if (!container) return; attempts = attempts || 0; if (attempts > 60) return; const retry = (delay) => setTimeout(() => this._reenterFullscreen(container, attempts + 1), delay || 200); const pe = container.querySelector('.plyr'); const p = container._player ? (container._player.player || null) : (container._hlsPlayer || null); if (!p || !p.fullscreen) { retry(); return; } // Sudah fullscreen di player ini (native atau fallback CSS)? if (p.fullscreen.active) { const nf = document.fullscreenElement; if (!nf || !pe || nf === pe || pe.contains(nf) || nf.contains(pe)) return; } // Player yang baru ditinggalkan harus keluar dulu -- baik native // (document.fullscreenElement) maupun fallback CSS (class), karena // di mode fallback document.fullscreenElement selalu null. this._exitOtherFullscreens(container); // Native: tunggu sampai browser benar-benar melepas fullscreen. if (document.fullscreenElement) { retry(); return; } // Frame tujuan harus sudah ter-render (Alpine x-show), kalau tidak // fullscreen ke elemen yang belum tampil akan kosong. const frame = container.closest('.watch-player-frame'); if (frame && window.getComputedStyle(frame).display === 'none') { retry(); return; } if (!pe) { retry(); return; } // Plyr's fullscreen.enter() TIDAK mengembalikan promise -> jangan pakai // .catch() (dulu melempar TypeError dan bikin re-enter tak pernah jalan). // requestFullscreen juga bisa ditolak (butuh user gesture), jadi hasilnya // dipantau lewat retry, bukan dianggap sukses begitu saja. try { if (!p.fullscreen.active) p.fullscreen.enter(); } catch (e) { retry(300); return; } retry(300); }, loadPage(page) { if (this._loadingGrid) return; this._loadingGrid = true; const params = new URLSearchParams({ page: page || 1, ep: this.currentEpisodeId, sort: this.epSort, search: this.epSearch }); fetch('/anime/boruto-naruto-next-generations/episodes/grid?' + params.toString()) .then(r => r.json()) .then(d => { if (this.$refs.sidebarEpList) this.$refs.sidebarEpList.innerHTML = d.sidebar_episodes; if (this.$refs.sidebarPag) this.$refs.sidebarPag.innerHTML = d.pagination; this._loadingGrid = false; }) .catch(() => { this._loadingGrid = false; }); }, applyEpSearch() { this.loadPage(1); } }" class="">
EP 104 - The Little Roommate

Laporkan Masalah

Pilih alasan untuk melaporkan episode ini.

Laporkan Masalah

Jelaskan masalah yang kamu temui agar tim kami bisa memperbaikinya.

Boruto: Naruto Generasi Selanjutnya

6.0 · TV · Complete
Boruto: Naruto Next Generations - Naruto was a young shinobi with an incorrigible knack for mischief. He achieved his dream to become the greatest ninja in the village and his face sits atop the Hokage monument. But this is not his story... A new generation of ninja are ready to take the stage, led by Naruto's own son, Boruto!

(Source: VIZ Media)

Nonton Boruto: Naruto Next Generations Episode 104 Subtitle Indonesia secara online dengan berbagai pilihan kualitas video, mulai dari 360p, 480p, 720p hingga 1080p. Pilih server dan resolusi yang sesuai untuk menikmati episode Boruto: Naruto Next Generations dengan subtitle Indonesia di perangkat kamu.

Studio Pierrot

Komentar (0)

atau untuk berkomentar.

Belum ada komentar. Jadilah yang pertama!

Pilih lagu untuk diputar

· NC

Belum ada theme songs

Daftar Episode

atau login dengan email

Lupa password?
atau daftar dengan email

Username ini sudah dipakai. Pilih username lain.

Hanya huruf kecil, angka, dan underscore (_) tanpa spasi.

Username tersedia!