哔哩哔哩宽屏

哔哩哔哩宽屏体验

当前为 2023-09-10 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name          哔哩哔哩宽屏
// @namespace     https://greasyfork.org/users/1125570
// @description   哔哩哔哩宽屏体验
// @version       0.1.4
// @author        posthumz
// @license       MIT
// @match         http*://*.bilibili.com/video/*
// @match         http*://*.bilibili.com/bangumi/*
// @match         http*://*.bilibili.com/list/*
// @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, .playlist-container, .main-container {
  z-index: 0;
  padding: 0 30px;
}

.left-container, playlist-container--left {
  flex: 1;
}

/* 番剧/影视页下方容器 */
.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;
}

#app > #biliMainHeader {
  height: initial !important;
}

#biliMainHeader > .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') {
        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('宽屏模式成功启用')
})()