恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Vue 3 自定义视频播放器控制器:从0到1完整实现指南

  • 首页
  • 资讯中心
  • /
  • Vue 3 自定义视频播放器控制器:从0到1完整实现指南

相关资讯

Python中文情感分析实战:SnowNLP+词云快速分析影视评论 2026/10/16 2:22:15
无人机红外目标检测:YOLO+Django实时检测系统实践 2026/10/16 2:22:15
柔性太阳能板与玻璃太阳能板怎么选:7 个关键差异 2026/10/16 2:22:15

最新资讯

Java部署配置隔离实战:从Profile到环境变量与配置中心
21:角谷猜想
BP神经网络陷入局部极小?用GA与PSO优化初始权值,附MATLAB实现
QTableWidget加载5万行卡死?根因分析与QTableView模型优化实战
告别三天打鱼两天晒网:一套可量化、能复盘的打卡系统设计
Python数据分析与可视化源码包实践:环境搭建到图表输出全链路

今日推荐

Java八种基本类型深度解析:从内存布局到实战选型
基础:常用快捷键——IDEA、VS Code、PyCharm 与 Linux 终端效率速查
解决ultraedit下verilog .v文件中文注释乱码:把编码配置改到TaoToken辅助排查

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

Vue 3 自定义视频播放器控制器:从0到1完整实现指南

发布时间:2026/10/16 2:22:15
Vue 3 自定义视频播放器控制器:从0到1完整实现指南 如果你的项目还在用video标签自带的controls那播放器这块大概率是“能播但没法看”。原生控件在不同系统上长相差得远不说功能还锁得死死的——想改个进度条样式、加个倍速菜单、做个音量手势基本无从下手。最近我在某项目组做视频管理后台时把播放器控制器彻底换成了 Vue 自研的 controller从播放暂停、进度拖拽、音量调节到倍速、全屏、画中画、快捷键全部收编。这篇就按“需求拆解 → 结构设计 → 核心逻辑 → 实操排查 → 进阶玩法”的顺序把整个从 0 到 1 的实现过程完整写出来涉及代码都基于 Vue 3 script setup组合式 API。1. 控制器整体设计与需求拆解1.1 为什么必须抛弃原生 controls原生 controls 最大的问题不是丑而是不可控。产品要的交互往往跟系统默认控件对不上比如希望进度条能在拖拽时实时预览时间点击音量图标改成进入某个静音档位或者把“倍速”和“画质切换”直接放进控制栏里。原生控件不开放样式接口也无法嵌入业务菜单所以唯一的路径就是自己实现一套。我的做法是给video设置controls属性为false但保留playsinline、webkit-playsinline、preloadmetadata等初始属性避免移动端自动全屏和加载策略出问题。控制器的显示完全独立成组件通过 props 接收 video 实例父子之间用事件通信而不是直接操作 DOM。这一步看起来很基础但对后续所有功能的扩展都至关重要——组件划分越彻底后面加功能就越轻松。1.2 功能边界与模块划分动手写代码前先列需求清单跟产品确认好控制栏里到底放哪些东西避免边做边加把结构搞乱。我这里最终圈定了 6 个核心功能区播放/暂停按钮、当前时间与总时长显示、可拖拽进度条、音量调节含静音切换、倍速选择菜单、全屏按钮。额外扩展区是键盘快捷键、画中画和播放进度记忆。组件拆分成三个外层的PlayerController.vue负责整体布局和状态管理ProgressBar.vue专门处理进度条展示与拖拽逻辑VolumeControl.vue管音量滑块与静音按钮。这样划分的好处在于进度条和音量条本身都是“滑块”交互细节较多单独成组件后可以各自维护自己的指针状态不会跟外层播放状态互相缠绕。如果你后续还要加弹幕、字幕开关直接在控制器里占位扩展即可。1.3 组件骨架与实例绑定控制器的核心数据源只有一个video实例。这个实例在画面组件里创建通过 ref 传给 controller。我在控制器的onMounted里绑定视频的各类事件在onBeforeUnmount里统一解绑避免内存泄漏。事件监听统一走一个bindEvents函数把回调函数存到配置对象里解绑时直接遍历调用写起来比散落一堆video.addEventLister干净得多。const videoRef ref(null) const video computed(() videoRef.value) const events { timeupdate: handleTimeUpdate, durationchange: handleDurationChange, progress: handleProgress, play: handlePlay, pause: handlePause, ended: handleEnded, volumechange: handleVolumeChange, loadedmetadata: handleLoadedMetaData, }绑定的事件名和回调集中管理后后续排查问题只需要看这些注册关系不需要在组件里到处搜索监听代码。实际开发中这个习惯帮了大忙尤其是当视频组件多了以后事件注册分散会变成调试噩梦。2. 播放核心逻辑状态同步与控制2.1 播放暂停状态管理与按钮联动播放暂停的实现本身很简单难点在状态同步。video元素自己的play()和pause()是异步的尤其play()在资源未加载完成时可能返回 Promise 且被 reject所以不能只依赖按钮点击事件来切换状态必须监听play和pause事件同步按钮图标。我的处理方式是状态字段isPlaying由事件驱动不直接由点击事件翻转。点击按钮时调用togglePlay()里面先判断video.paused再执行对应的播放或暂停方法并捕获play()的 Promise 异常防止未加载完成时点击导致控制台报错。这里有个细节当video.play()返回的 Promise 被 reject 时如果不拦截浏览器的报错会直接影响开发调试用户看到的只是按钮没有任何反应。2.2 时间显示与播放信息格式化时间显示需要把秒数格式化成mm:ss甚至hh:mm:ss不能用toFixed直接拼会漏一位数。我记得最早写播放器时直接用Math.floor(currentTime) : Math.remainder结果1:5这种显示看着很业余。后来封装了一个formatTime函数补零逻辑统一处理时长和当前时间都用同一个函数。const formatTime (time) { if (isNaN(time)) return 00:00 const h Math.floor(time / 3600) const m Math.floor((time % 3600) / 60) const s Math.floor(time % 60) const mm String(m).padStart(2, 0) const ss String(s).padStart(2, 0) return h 0 ? ${h}:${mm}:${ss} : ${mm}:${ss} }总时长的获取要等loadedmetadata事件触发后才能拿到duration在这之前界面上显示00:00。如果资源是跨域且未设置正确的CORS头duration可能拿不到这在后面排查里会展开讲。2.3 音量控制与静音状态处理音量的有效范围是0~1原生input typerange的step设为0.05拖动滑块时把video.volume赋成当前值。要注意的是video.muted跟video.volume0是两回事静音状态切换最好单独用muted属性控制否则等你取消静音时音量还是 0用户会很困惑。我的静音按钮点击逻辑是记录一个内部变量lastVolume默认1。静音时先把lastVolume video.volume再设video.muted true取消静音时先video.muted false再判断video.volume 0就恢复为lastVolume。这样即使音量已经被拖到 0再点静音按钮取消时也能恢复到一个合理音量。音量图标也需要根据静音状态和音量值切换不能只判断muted。2.4 倍速播放与菜单实现倍速功能通过video.playbackRate实现这是浏览器原生就支持的能力。我提供[0.5, 0.75, 1, 1.25, 1.5, 2]几个档位点击菜单项后设置playbackRate并关闭菜单。这里有一个常见的坑视频暂停时修改播放倍速图标和菜单显示都对但一旦继续播放有些浏览器的playbackRate会被重置回1。解决方式是在play事件触发后再执行一次video.playbackRate currentRate的赋值确保实际播放倍速跟随用户选择。3. 核心交互实现进度条、拖拽与手势3.1 可点击、可拖拽进度条进度条是整个播放器交互最密集的地方需要同时支持点击跳转和拖拽跳转。实现的核心思路是用一个相对定位的容器计算鼠标位置相对于进度条宽度所占的百分比再乘以video.duration得到目标时间点最后设置video.currentTime。难点在于拖拽过程中不能频繁触发seek操作。timeupdate事件本身也是后台在更新进度如果你拖拽的时候又去更新进度条位置会造成“拖到哪儿之后又被拽回去”的抖动。我这里的处理是引入局部状态isDragging拖拽开始时置为true期间进度条的显示位置完全由鼠标坐标驱动不接收timeupdate的更新鼠标抬起时再一次性设置currentTime。const onPointerDown (e) { isDragging.value true video.pause() window.addEventListener(pointermove, onPointerMove) window.addEventListener(pointerup, onPointerUp) updateProgressFromEvent(e) }拖拽期间还要处理 seek 的缓冲卡顿问题如果视频没有缓冲到目标位置currentTime设置后画面会保持黑屏但timeupdate事件可能在等待加载时不触发进度条会停在 0。更好的做法是拖拽结束时调用video.currentTime targetTime后立即把 UI 上的当前位置更新为目标时间不能依赖timeupdate恢复同步。3.2 缓冲进度显示与进度条层级进度条通常分三层已播放、缓冲、剩余。缓冲值来源于video.buffered对象需要在progress事件触发时计算当前播放位置对应的缓冲末尾。const handleProgress () { if (!video.value || !video.value.duration) return const buffered video.value.buffered if (buffered.length 0) { bufferedEnd.value buffered.end(buffered.length - 1) / video.value.duration } }这层缓冲条的意义在于让用户知道拖拽到某个位置时是否需要等待。如果缓冲条没有正确更新用户拖到未缓冲区域后没有视觉反馈会被误认为是播放器卡死。实际开发中还有一个细节某些流媒体比如 HLS的buffered数据是分段的buffered.end(buffered.length - 1)可能不等于当前播放位置需要遍历找到包含currentTime的区间否则缓冲进度显示会偏向最后一个缓冲段视觉上就很怪。3.3 控制栏显隐与点击切换控制栏不能一直显示否则会遮挡视频内容。常见的做法是监听容器mouseenter和mouseleave鼠标移入时显示控制栏并重置一个计时器鼠标移出后延时2.5s隐藏。但这里有个坑mouseleave后直接隐藏用户只是稍微移出想看看其他区域控制栏就消失了再回来又要等mouseenter触发体验很跳。我的方案是设置一个hideTimer每次触发显示时都clearTimeout并重新setTimeout隐藏鼠标移出后如果 2 秒内没有移回自然隐藏移回则取消隐藏计时器。播放过程中如果没有鼠标活动隐藏控制栏视频暂停时则始终显示控制栏因为暂停状态下用户需要操作按钮藏起来很不合理。移动端没有mouseenter需要额外监听触摸事件来决定显隐这个我在后面兼容性部分说。4. 实操过程从零搭建自定义控制器4.1 第一步搭建基础播放器外壳先写一个最基础的视频播放器外壳video元素本身不做任何控制逻辑只负责输出图像和触发事件。控制器组件通过 slot 或者组件传参的方式接收 video 实例引用。我这里为了让代码更直接采用provide/inject方式注册playerContext子组件统一从这个上下文里取video实例避免一层层 props 透传。template div classvideo-player video refvideoRef clicktogglePlay / PlayerController v-ifshowController / /div /template这里马上遇到一个问题v-ifshowController如果直接用布尔值绑定控制器一开始就被销毁事件的绑定和解绑要集中在PlayerController里管理数据流会很绕。我采用的方式是v-show控制视觉隐藏组件始终挂载。这样视频实例的传入和事件绑定在一开始就完成不至于因为隐藏导致注册时机不稳定。4.2 第二步事件绑定与响应式状态更新事件绑定是控制器能否正确同步的关键。开发时我踩过一个典型问题给video绑定timeupdate后用ref存储currentTime但控制器的进度条更新经常会滞后一拍。原因在于timeupdate事件本身就有限频大概 4 次/秒每次触发时的currentTime可能跟渲染帧不同步。处理方法是增加一个requestAnimationFrame循环在播放状态下持续更新进度条的位置而不是依赖事件触发的瞬间渲染。const updateLoop () { if (!video.value || video.value.paused) return currentTime.value video.value.currentTime animationFrame.value requestAnimationFrame(updateLoop) }这里也要注意如果只是用timeupdate更新显示不追求极致平滑其实够用。但做自定义控制器时进度条上的小圆点如果不跟着 60fps 更新拖拽时视觉会有明显的跳跃。所以我在播放状态下启动requestAnimationFrame更新暂停或结束时取消效果提升非常明显。4.3 第三步全屏、画中画与实际环境适配全屏切换用requestFullscreen但需要兼容 WebKit 内核的前缀方法。容器全屏与 video 元素全屏的差异在于容器的控制栏和视频是一个整体全屏后主要操作区是容器本身。const toggleFullscreen () { const el containerRef.value if (!document.fullscreenElement) { el.requestFullscreen?.() } else { document.exitFullscreen?.() } }webkit前缀方法在 iOS Safari 上特别重要不处理的话点击全屏直接没反应。另一个问题是 PC 端按 Esc 退出全屏需要监听fullscreenchange来同步按钮状态不能只靠点击事件。画中画功能直接用video.requestPictureInPicture()但要在支持的浏览器中判断document.pictureInPictureEnabled再显示按钮。画中画在播放器实际场景中很适合“一边看视频一边做笔记”的需求但实现前要确认目标浏览器不然功能藏起来比报错更好。4.4 第四步播放进度记忆与本地存储做视频管理后台时用户经常看到一半退出去再次打开希望接着看。我的做法是监听pause和ended事件把currentTime存入localStoragekey 按视频 url 的 hash 生成点击播放时读取并直接从该位置开始。const saveProgress () { if (!video.value) return const key playback:${videoKey.value} localStorage.setItem(key, String(video.value.currentTime)) }有个细节容易被忽略ended事件触发后如果继续存储进度会把duration当进度存进去下次打开直接从视频末尾开始播放器以为已经看完了黑屏等待重新加载。我的处理是在ended时清除该 key 的存储值表示这条视频已经完整看完。另外如果视频是直播流或者音频直播currentTime没有意义存储逻辑需要用duration ! Infinity做条件过滤。5. 关键设计细节与源码逻辑深度解读5.1 拖动进度条时的时间预览与节流拖拽进度条时很多播放器会显示一个气泡预览目标时间。实现方式是在pointermove期间根据百分比乘上duration再格式化显示在气泡里。但要避免每次pointermove都执行时间格式化函数会有微小性能浪费。我在实际开发中加了一个简单节流每帧只做一次更新时间格式化的结果直接用ref保存模板只读这个值。let lastUpdateTime 0 const onPointerMove (e) { const now performance.now() if (now - lastUpdateTime 100) return lastUpdateTime now previewTime.value percentage * video.value.duration }5.2 拖拽结束后的缓冲策略与状态复位拖拽到未缓冲的位置时video.currentTime设置后需要等待缓冲完成才能播放。如果此时直接调用play()部分浏览器会卡在 loading 状态。我的方案是拖拽结束后不立即播放而是把isPlaying状态先置为false然后等待seeked事件触发后再恢复播放。seeked事件表示视频已经跳转完成此时再调用play()会比直接播放更稳。const onSeeked () { if (shouldResume.value) { video.value.play() isPlaying.value true shouldResume.value false } }5.3 控制栏动画性能与 CSS 实现技巧控制栏的显隐如果直接用v-if或者display: none动画过渡会失效。我用的方案是保留控制栏的opacity和translateY过渡显隐操作只变换这两个属性并用pointer-events控制控制栏在隐藏状态下不可点击。这样既能保留平滑的滑出动画又不会导致隐藏在角落的按钮被意外触发。.player-control { opacity: 0; transform: translateY(20px); pointer-events: none; transition: opacity 0.3s, transform 0.3s; } .player-control.show { opacity: 1; transform: translateY(0); pointer-events: auto; }5.4 键盘快捷键与全局事件冲突处理键盘快键是控制器的高级扩展通常希望空格控制播放暂停、左右方向键控制快退快进、上下方向键调整音量。全局绑定keydown时要注意如果你把事件绑定在document上用户在输入框里打字按空格也会触发播放暂停这很反直觉。我的做法是事件绑定到播放器容器的tabindex播放器获得焦点时才启用快捷键或者在回调里判断e.target.tagName是否属于 input、textarea是则直接跳过。const handleKeydown (e) { const tag e.target.tagName if (tag INPUT || tag TEXTAREA) return switch (e.code) { case Space: e.preventDefault() togglePlay() break case ArrowLeft: seekBy(-5) break case ArrowRight: seekBy(5) break case ArrowUp: changeVolume(0.1) break case ArrowDown: changeVolume(-0.1) break } }6. 常见问题与排查技巧实录6.1 iOS Safari 与控制器的兼容性坑iOS 上的video不设置playsinline时会自动全屏播放控制层直接被系统接管自定义控制器完全没有展示机会。排查时先看 HTML 属性是否带playsinline webkit-playsinline同时 iOS 的音量调节在视频播放下被系统限制video.volume的设置值会被忽略除非静音。所以音量滑块在 iOS 上通常只能做静音切换滑块拖动反而会造成“拖动无效果”的错觉最好在移动端判断isIOS后隐藏音量条只留静音按钮。6.2 跨域视频导致 loadedmetadata 不触发如果视频资源与页面域名不同且没有配置 CORS 头video元素的duration会有获取不到的风险loadedmetadata事件虽然可能触发但duration返回NaN。排查时看控制台是否有 CORS 错误。解决方案要么后端加上Access-Control-Allow-Origin响应头要么在video标签上显式设置crossOriginanonymous属性。crossOrigin属性直接影响video是否允许跨域读取媒体数据比如取帧截图、读取 buffered 范围、计算精确时长等。如果不加这个属性很多播放器功能在跨域视频下都会失效这是视频项目中比较隐蔽的坑。6.3 自动播放策略控制与静音播放浏览器对自动播放有严格的策略限制带声音自动播放几乎被所有主流浏览器禁止。实现页面上常见的“进入即播放”效果最稳妥的方法是先video.muted true再调用play()。用户点击播放按钮时再恢复音量。响应式设计上有一个小技巧如果play()返回的 Promise 被 reject不要立即报错而是把播放按钮的状态置为“需要用户点击”静默等待用户交互。const tryAutoPlay async () { try { await video.value.play() } catch (e) { console.warn(autoplay blocked, waiting for user interaction) } }6.4 常见问题速查表现象可能原因排查与解决控制栏不显示v-if控制导致组件未挂载改用v-show确保事件绑定时机稳定进度条拖拽后跳回原位置拖拽时未隔离timeupdate更新拖拽期间设置isDragging并屏蔽事件更新视频黑屏且进度条在走视频源未缓冲到该位置显示缓冲进度等待seeked后再播静音后取消无声没有记录lastVolume实现静音前后音量值保存与恢复iOS 点击全屏无反应缺少webkitRequestFullscreen兼容处理全屏 API并检测移动端倍速设置后重新播放失效playbackRate被重置在play事件中重新赋值倍速拖拽进度条时画面卡顿seek 频繁触发拖拽结束后一次性设置currentTime多个播放器同时注册事件未在销毁时解绑onBeforeUnmount中统一移除事件6.5 移动端触摸手势与控制栏自动隐藏移动端的控制栏显隐逻辑不能依赖鼠标事件。我在touchstart时显示控制栏然后启动隐藏计时器touchmove时重置计时器视频区域双击时切换播放状态。为了更符合移动端操作习惯我在控制栏左右两侧各放了一个透明区域单击左侧快退 10 秒右侧快进 10 秒。这个交互模仿了一些成熟播放器 App 的通用手势用户上手很快。const handleTouch () { showControls() resetHideTimer() }移动端还有一个特殊点点击事件会延迟约 300ms需要给视频容器设置touch-action: manipulation来消除延迟。这里不是简单加一个click就完事需要在样式中配合。7. 拓展从播放器到播控能力的组合7.1 自定义事件与外部状态通信播放器控制器不应该是一个孤立组件它需要对外暴露当前播放状态、进度、倍速等数据。我的做法是使用mitt这类轻量事件总线或者直接通过defineEmits向父组件派发事件。事件命名要统一比如play、pause、progress、fullscreen-change等父组件页面可以据此做埋点、关联推荐、自动下一集等业务逻辑而不是在控制器内部直接写死了所有业务依赖。7.2 如何接入数据统计与业务联动播放器上线后一般都要接入数据统计比如播放时长、完播率、卡顿率、点击控制栏事件。我在自定义控制器的关键动作里统一调用了一个reportEvent(type, data)函数数据上报逻辑集中管理。这样后续如果要接新的统计平台只需要改内部实现不必在控制器里到处埋点。7.3 未来可扩展清晰度切换、字幕与广告自定义控制器的价值就在于可扩展。清晰度切换本质上是更换视频源地址后重新加载此时需要保留当前播放位置、倍速、音量并且在切换完成后无缝播放。字幕功能的实现方式是使用track元素或自定义渲染层跟控制器的耦合度很低可以独立开发。广告逻辑通常是在播控核心上增加一个 preroll 状态广告期间禁用控制栏的部分按钮这些都可以基于这个架构逐步加进去。8. 项目落地中的设计细节与优化技巧8.1 通过拦截原生控制点保持交互统一除了隐藏controls还需要关注video元素上一些默认行为。比如右键菜单会带有浏览器自带的播放器控制项如果不处理右键弹出菜单会让用户以为页面失控。我通常监听contextmenu事件并preventDefault然后自己弹出一个包含“循环播放”“画中画”“复制视频地址”等组件的自定义菜单。这样所有交互都在自己掌控范围内也更贴合整体 UI 风格。8.2 组件复用的抽象技巧如果项目里有多个页面都需要播放器不建议每处都复制一份控制器代码。我把播放器封装成一个VideoPlayer组件提供src、poster、autoplay、showControls等 props内部统一处理事件绑定和 UI 渲染。页面里只关心播放什么视频和拿到什么状态。这种封装让播放器的维护成本大幅下降后续对控制器的改动只影响一处不会牵连到各业务页面。VideoPlayer :srcvideoUrl :posterposterUrl :show-controllertrue playhandlePlay endedhandleEnded /8.3 状态驱动的 UI 渲染与性能优化控制器里的按钮状态、图标切换、进度条展示全部由响应式状态驱动避免手动操作 DOM 带来的 bug。比如isPlaying决定播放/暂停按钮图标volume决定音量条宽度和静音图标。因为整个控制器只依赖少量响应式数据性能上没有问题。唯一需要注意的是requestAnimationFrame循环在视频暂停后要取消否则会一直空转浪费 CPU。8.4 响应式布局适配不同宽度控制栏在不同屏幕宽度下不能把全部按钮挤成一团。我做了三档布局宽度宽屏显示完整控制栏播放、进度、时间、音量、倍速、全屏中等宽度隐藏音量条和倍速菜单文字窄屏只保留播放、进度、时间和全屏按钮。用 CSS 媒体查询配合justify-content空间分配不需要 JS 动态计算。这样既简单又稳定避免频繁触发resize事件。9. 总结与后续演进我个人的最大体会是自定义播放器控制器并不是“把 controls 属性关掉再画几个按钮”这么简单它本质上是一个小型的播放状态机所有交互都要基于视频事件来驱动而不是基于用户点击来猜测状态。这也是很多新手初次实现时长出各种同步 bug 的根本原因。事件驱动、状态中心化、交互解耦这三点是整篇文章最值得带走的沉淀。后续我打算在这个控制器的架构上继续扩展直播流的播控逻辑以及把控制器的样式配置化输出成主题变量让不同项目通过配置切换配色和布局。如果你也在做自己的播放器建议先跟着这篇文章搭一个最小可用的控制器骨架再逐步加功能不要一开始就把倍速、画中画、记忆播放全部塞进去那样调试起来困难出现问题也很难定位。从一个小而完整的控制器开始是最稳的路线。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号