哔哩哔哩宽屏

哔哩哔哩宽屏体验

目前為 2023-09-05 提交的版本,檢視 最新版本

// ==UserScript==
// @name          哔哩哔哩宽屏
// @namespace     https://greasyfork.org/users/1125570
// @description   哔哩哔哩宽屏体验
// @version       0.1.2
// @author        posthumz
// @license       MIT
// @match         http*://*.bilibili.com/video/*
// @match         http*://*.bilibili.com/bangumi/*
// @icon          https://www.bilibili.com/favicon.ico
// @run-at        document-end
// @grant         GM_addStyle
// @grant         GM_getValue
// @grant         GM_setValue
// @grant         GM_registerMenuCommand
// @noframes
// ==/UserScript==

(async () => {
  'use strict'

  const styles = `body {
  overflow-x: hidden !important; /* 以防内容超出宽度 */
}

/* 播放器 */
#bilibili-player {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
}

#playerWrap,
#bilibili-player-wrap {
  position: relative;
  height: 100vh;
  padding: 0;
}

/* 视频标题换行显示 */
#viewbox_report {
  height: auto;
}

.video-title {
  white-space: normal !important;
}

/* 视频页下方容器 */
.video-container-v1, .main-container {
  z-index: 0;
  padding: 0 30px;
}

.left-container {
  width: 100% !important;
}

/* 番剧/影视页下方容器 */
.main-container {
  width: calc(100% - 75px);
  margin: 15px 50px 15px 25px !important;
  padding: 0;
}

.toolbar {
  padding-top: 0;
}

.plp-r.sticky {
  left: calc(var(--containerWidth) + var(--right-bar-width)) !important;
}

/* 播放器控件 */
.bpx-player-control-bottom-left,
.bpx-player-control-bottom-right,
.bpx-player-sending-bar,
.be-video-control-bar-extend {
  display: flex;
  gap: 16px;
}

.bpx-player-control-bottom-left > *,
.bpx-player-control-bottom-right > *,
.bpx-player-sending-bar > *,
.be-video-control-bar-extend > * {
  width: auto !important;
}

.bpx-player-ctrl-time-label {
  text-align: center !important;
  text-indent: 0 !important;
}

.bpx-player-ctrl-time, .bpx-player-ctrl-quality {
  margin-right: 0 !important;
}

.bpx-player-video-info {
  margin-right: 0 !important;
  display: flex !important;
}

/* 右下方浮动按钮 */
div[class^=navTools_floatNav] {
  z-index: 1 !important;
}

/* 笔记位移 (不然笔记会超出页面初始范围) */
.note-pc {
  transform: translate(-12px, 64px);
}

/* 导航栏 (兼容Bilibili Evolved自定义导航栏) */
#biliMainHeader,
.custom-navbar {
  position: sticky !important;
  top: 0;
  z-index: 2 !important;
}

#biliMainHeader {
  height: initial !important;
}

.bili-header {
  min-height: 0 !important;
}

.bili-header__bar {
  position: relative !important;
}

/* Bilibili Evolved 夜间模式修正 */
.bpx-player-container .bpx-player-sending-bar {
  background-color: transparent !important;
}

.bpx-player-container .bpx-player-video-info {
  color: hsla(0,0%,100%,.9) !important;
}

.bpx-player-container .bpx-player-sending-bar .bpx-player-video-btn-dm,
.bpx-player-container .bpx-player-sending-bar .bpx-player-dm-setting,
.bpx-player-container .bpx-player-sending-bar .bpx-player-dm-switch {
  fill: hsla(0,0%,100%,.9) !important;
}

.be-settings {
  z-index: 3;
  position: fixed;
}`
  // 播放器
  const player = document.getElementById('bilibili-player')
  // 主容器,视频播放页为#app,番剧/影视播放页为.home-container
  const home = document.getElementById('app') || document.getElementsByClassName('home-container')[0]
  // 播放器外容器,视频播放页为#playerWrap,番剧/影视播放页为#bilibili-player-wrap
  const wrap = document.getElementById('playerWrap') || document.getElementById('bilibili-player-wrap')

  // 播放器不存在时不执行
  if (!player) { return console.info('未找到播放器,不启用宽屏模式') }

  // 在新版本页面,播放器存在时都应该存在
  if (!wrap || !home) { return console.error(
    `页面加载错误:${[
      wrap ? '' : '播放器外容器',
      home ? '' : '主容器',
    ].filter(Boolean).join(', ')},请检查是否为新版页面`
  ) }

  // 每一定时间检测某个条件是否满足,超时则reject
  const waitFor = (
    isloaded = () => true, // 初始值只是为了类型检查,实际上用不到
    interval = 100,
  ) => new Promise((resolve, reject) => {
    var retry = 100
    const intervalID = setInterval(() => {
      if (--retry == 0) {
        console.error('页面加载超时')
        clearInterval(intervalID)
        return reject()
      }
      const element = isloaded()
      if (element) {
        clearInterval(intervalID)
        return resolve(null)
      }
      if (retry % 10 == 0) {
        console.debug('等待页面加载')
      }
    }, interval)
  })

  // 等待人数加载
  const b = player.getElementsByTagName('b')
  await waitFor(() => {
    const content = b[0]?.textContent
    return content != null && content != '-'
  })

  // 导航栏 (兼容Bilibili Evolved自定义顶栏,有可能延后加载)
  const navigation = await (async () => {
    const header = document.getElementById('biliMainHeader')
    if (header) {
      // 将导航栏移至主容器最前
      home.insertAdjacentElement('afterbegin', header)

      // bili-header__bar不可见时使用自定义顶栏
      const headerBar = header.getElementsByClassName('bili-header__bar')[0]
      if (headerBar && window.getComputedStyle(headerBar).display == 'none') {
        headerBar.parentElement?.style.setProperty('min-height', '0', 'important')
        header.style.setProperty('height', 'initial', 'important')
        const navbar = document.getElementsByClassName('custom-navbar')
        await waitFor(() => Boolean(navbar[0]))
        return home.insertAdjacentElement('afterbegin', navbar[0])
      }
    }
    return header
  })()


  // 播放器内容器
  const container = player.getElementsByClassName('bpx-player-container')[0]
  // 播放器底中部框 (用于放置弹幕框内容)
  const bottomCenter = (() => {
    const center = player.getElementsByClassName('bpx-player-control-bottom-center')[0]
    // 番剧版使用squirtle-controller-wrap-center,但也存在bpx-player-control-bottom-center
    // 所以通过检测前一个元素(bpx-player-control-bottom-left)是否有子元素来判断使用哪个
    return center?.previousElementSibling?.hasChildNodes() ? center
      : player.getElementsByClassName('squirtle-controller-wrap-center')[0]
  })()

  // 弹幕框
  const danmaku = player.getElementsByClassName('bpx-player-sending-bar')[0]

  // 正常情况应该都存在
  if (!navigation || !container || !bottomCenter || !danmaku) { return console.error(
    `页面加载错误:${[
      navigation ? '' : '导航栏',
      container ? '' : '播放器内容器',
      bottomCenter ? '' : '播放器底中部框',
      danmaku ? '' : '弹幕框',
    ].filter(Boolean).join(', ')}`
  ) }

  // 视频置于导航栏上方
  navigation.insertAdjacentElement('beforebegin', wrap)

  // 使用宽屏样式 (除非当前是小窗模式)
  if (container.getAttribute('data-screen') != 'mini') {
    container.setAttribute('data-screen', 'web')
  }

  // 重载container的setAttribute:data-screen被设置为mini(小窗)以外的值时将其设置为web(宽屏)
  const setAttributeContainer = container.setAttribute.bind(container)
  container.setAttribute = (name, value) =>
    setAttributeContainer(name, name == 'data-screen' && value != 'mini' ? 'web' : value)

  // 番剧页面需要初始与退出全屏时移除#bilibili-player-wrap的class
  if (wrap.id == 'bilibili-player-wrap') {
    wrap.className = ''
    document.addEventListener('fullscreenchange',
      () => { document.fullscreenElement || (wrap.className = '') }
    )
  }

  // 退出全屏时弹幕框移至播放器下方
  document.addEventListener('fullscreenchange',
    () => { document.fullscreenElement || bottomCenter.replaceChildren(danmaku) }
  )

  // 添加自定义样式
  GM_addStyle(styles)

  // 移除原 宽屏/网页全屏 按钮,因为其功能与宽屏样式冲突
  for (const className of [
    'bpx-player-ctrl-wide', 'bpx-player-ctrl-web',
    'squirtle-widescreen-wrap', 'squirtle-pagefullscreen-wrap',
  ]) { player.getElementsByClassName(className)[0]?.remove() }

  // 改变导航栏位置,true为视频下方,false为视频上方
  const lowerNavigation = (value = true) => {
    if (value) {
      wrap.style.setProperty('height', '')
      wrap.insertAdjacentElement('afterend', navigation)
    } else {
      wrap.style.setProperty('height', `calc(100vh - ${navigation.clientHeight}px)`)
      wrap.insertAdjacentElement('beforebegin', navigation)
    }
    return (GM_setValue('lowerNavigation', value), value)
  }
  // 检测导航栏设置,如果是false则上置导航栏,默认为false
  if (!GM_getValue('lowerNavigation', true)) { lowerNavigation(false) }

  // 将弹幕框移至播放器下方一次
  bottomCenter.replaceChildren(danmaku)

  // 添加切换导航栏位置的菜单项
  GM_registerMenuCommand('切换导航栏位置', () =>
    GM_setValue('lowerNavigation', lowerNavigation(!GM_getValue('lowerNavigation', true)))
  )
  const note = document.getElementsByClassName('note-pc')[0]
  if (note) {
    // 将笔记移至主容器,不然会被视频和导航栏遮挡
    navigation.insertAdjacentElement('afterend', note)
  }

  console.info('宽屏模式成功启用')
})()