OSS下载时,出现206

本文讲述了在Java中使用HttpURLConnection从网络下载文件时遇到206PartialContent返回码的情况,介绍了如何设置Range请求头以支持断点续传,包括获取已下载字节数、计算剩余大小并进行实际下载操作。

该文章用于备忘。

背景描述

  • java从网络链接下载文件
  • 容器weblogic 10.3.6
  • 后台跑

问题描述

  • 下载网络文件时,出现非200情况
  • 返回码 206
  • 返回信息 Partial Content

备忘

  1. 206属于同200一样的返回码
  2. 不同点在于,200是一次性返回结果;206是返回了一部分字节
  3. 通常返回206 Partial Content的服务器,可认为是支持断电续传的
  4. 打开链接后,需要设置range
HttpURLConnection connection = null;            
URL url = new URL(urlStr);
connection = (HttpURLConnection)url.openConnection();
// 已经下载的字节数
long alreadySize = 0;
File file = new File(downloadPath);
if (file.exists()) {
// 如果文件存在,就获取当前文件的大小
	alreadySize = file.length();
}
connection.setRequestProperty("Range", "bytes=" + alreadySize + "-");
connection.connect();
int responseCode = connection.getResponseCode();
String responseMessage = connection.getResponseMessage();

如果返回码为206,则

 if(responseCode == 206) {
                // 获取未下载的文件的大小
                // 本方法用来获取响应正文的大小,但因为设置了range请求头,那么这个方法返回的就是剩余的大小
                long unfinishedSize = connection.getContentLength();
                // 文件的大小
                long size = alreadySize + unfinishedSize;

                // 获取输入流
                InputStream in = connection.getInputStream();
                // 获取输出对象,参数一:目标文件,参数2表示在原来的文件中追加
                OutputStream out = new BufferedOutputStream(new FileOutputStream(file, true));

                // 开始下载
                byte[] buff = new byte[4096];
                int len;
                logger.error(downloadPath + " 下载开始");
                while ((len = in.read(buff)) != -1) {
                    out.write(buff, 0, len);
                    alreadySize += len;
                }
                out.close();
                logger.error(downloadPath + " 下载结束");
                return true;
            }

<template> <div style="width: 100%"> <div class="page-header"> <div class="page-title"> <el-page-header @back="goBack" content="历史播放"></el-page-header> </div> </div> <el-container class="table-list" style="position: relative"> <el-aside width="300px"> <div class="record-list-box-box"> <div class="search-input-button"> <el-date-picker size="mini" v-model="chooseDate" type="date" value-format="yyyy-MM-dd" placeholder="日期" @change="dateChange()"></el-date-picker> <el-button size="mini" icon="el-icon-refresh" circle @click="dateChange()"></el-button> </div> <div v-show="videoUrl === ''&&recordsLoading === false" class="record-list-no-val">暂无视频</div> <div v-show="recordsLoading" class="loading-anim-box"> <loadings></loadings> </div> </div> </el-aside> <el-main style="padding-bottom: 10px;"> <div style="display: flex;justify-content: center"> <div class="playBox"> <!-- <m3u8-player ref="m3u8Player" :src="videoUrl"/> --> <div id="videoPlay"/> </div> </div> <div class="player-option-box"> <div v-show="videoUrl"> <el-button-group> <el-time-picker size="mini" v-model="broadcastTime" value-format="HH:mm" placeholder="开始间点" :picker-options="timePickerOptions" @change="userChange"> </el-time-picker> </el-button-group> <el-button-group> <el-button size="mini" class="iconfont icon-zanting" title="暂停" @click="gbPause()"></el-button> <el-button size="mini" class="iconfont icon-kaishi" title="开始" @click="gbPlay()"></el-button> <!--<el-button size="mini" class="iconfont icon-xiazai1" title="下载选定录像" @click="downloadRecord()"></el-button>--> </el-button-group> </div> <!-- 间线 --> <div v-show="videoUrl"> <el-slider class="playtime-slider" v-model="playTime" id="playtimeSlider" :min="sliderMIn" :max="sliderMax" :format-tooltip="playTimeFormat" @change="playTimeChange" :marks="playTimeSliderMarks"> </el-slider> <div class="slider-val-box"> <div class="slider-val" v-for="item of detailFiles" :style="'width:' + getDataWidth(item) + '%; left:' + getDataLeft(item) + '%'"></div> </div> </div> </div> </el-main> </el-container> </div> </template> <script> import moment from 'moment' import M3u8Player from '../common/M3u8Player.vue' import loadings from "../common/loadings.vue"; import XGPlayer from 'xgplayer'; import 'xgplayer/dist/index.min.css'; import 'xgplayer/es/plugins/track/index.css'; import HlsPlugin from 'xgplayer-hls'; export default { name: 'history', components: { M3u8Player, loadings }, data() { return { recordsLoading: false, streamId: "", hasAudio: false, detailFiles: [], chooseDate: null, videoUrl: '', streamInfo: null, app: null, mediaServerId: null, ssrc: null, urlOriginal: null, player: null, // 播放器实例 sliderMIn: 0, sliderMax: 86400, playerBoxStyle: { "margin": "0 auto 20px auto" }, playTime: null, timeRange: null, startTime: null, endTime: null, videoDuration: null, playTimeSliderMarks: { 0: "00:00", 3600: "01:00", 7200: "02:00", 10800: "03:00", 14400: "04:00", 18000: "05:00", 21600: "06:00", 25200: "07:00", 28800: "08:00", 32400: "09:00", 36000: "10:00", 39600: "11:00", 43200: "12:00", 46800: "13:00", 50400: "14:00", 54000: "15:00", 57600: "16:00", 61200: "17:00", 64800: "18:00", 68400: "19:00", 72000: "20:00", 75600: "21:00", 79200: "22:00", 82800: "23:00", 86400: "24:00", }, broadcastTime: new Date(0, 0, 0), timePickerOptions: null, isRemain5MinTriggered: false, lastPosition: 0, // 上一个视频的播放位置(秒) lastDuration: 0, // 上一个视频的总长(秒) isFetchingNewVideo: false, // 防止重复请求的标志位 url1: 'https://linggan-xinlian.oss-cn-beijing.aliyuncs.com/41010500002000000002_41010500001320000517/replay.m3u8?x-oss-process=hls%2Fsign&x-oss-date=20250704T014120Z&x-oss-expires=108000&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5tPNiqn8Gwv4thoKTCPT%2F20250704%2Fcn-beijing%2Foss%2Faliyun_v4_request&x-oss-signature=f5c1e00e450ca1d074ebee548d61f0c2b169c45c376730789322a6dfe8437b8a', url3: 'https://linggan-xinlian.oss-cn-beijing.aliyuncs.com/41010500002000000003_41010500001320000314/replay.m3u8?x-oss-process=hls%2Fsign&x-oss-date=20250703T054514Z&x-oss-expires=108000&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5tPNiqn8Gwv4thoKTCPT%2F20250703%2Fcn-beijing%2Foss%2Faliyun_v4_request&x-oss-signature=498621f5b3335729dc63e7138c79fbfc2edefdcddd530a06261ed92102087881', url2: 'https://linggan-xinlian.oss-cn-beijing.aliyuncs.com/41010500002000000003_41010500001320000311/replay.m3u8?x-oss-process=hls%2Fsign&x-oss-date=20250704T022521Z&x-oss-expires=108000&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5tPNiqn8Gwv4thoKTCPT%2F20250704%2Fcn-beijing%2Foss%2Faliyun_v4_request&x-oss-signature=ad503cc4e6e3e94c542290cb4139d4dcaf8cecdfc95168c2f55d7373da3f310f' }; }, mounted() { this.chooseDate = moment().format('YYYY-MM-DD') this.initPlayer(); this.dateChange(); }, destroyed() { this.$destroy('recordVideoPlayer'); }, methods: { // 点击下载视频 // downloadRecord() { // const link = document.createElement('a'); // link.href = this.videoUrl; // link.setAttribute('download', this.chooseDate); // link.click(); // }, // 初始化视频 initPlayer() { if (!this.player) { this.player = new XGPlayer({ id: 'videoPlay', height: '100%', width: '100%', // url: this.videoUrl || this.url1, // 视频地址 url: this.videoUrl, // 视频地址 playbackRates: [0.7, 1.0, 1.5, 2.0], defaultPlaybackRate: 1.0, aspectRatio: '16:9', live: false, controls: true, autoplay: false, plugins: [HlsPlugin], }) // 监听元数据加载完成事件(备选) this.player.on('loadedmetadata', () => { this.videoDuration = Math.floor(this.player.duration); this.sliderMax = this.videoDuration; this.generateTimeMarks(); }); // 2. 监听播放进度更新(实触发) this.player.on('timeupdate', () => { this.handleProgressUpdate(); }); // 3. 监听播放结束事件(备选,更精准) this.player.on('ended', () => { this.handleVideoEnded(); }); } }, // 生成间标记 generateTimeMarks() { const marks = {}; const totalHours = Math.ceil(this.videoDuration / 3600); // 向上取整到小 // 为每个小生成标记 for (let hour = 0; hour <= totalHours; hour++) { const seconds = hour * 3600; // 格式化间为 HH:MM 格式 const timeStr = this.formatTime(seconds); marks[seconds] = timeStr; } this.playTimeSliderMarks = marks; this.updateTimePickerOptions(); }, // 格式化秒数为 HH:MM 格式 formatTime(seconds) { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); // 确保小和分钟为两位数 const formattedHours = hours.toString().padStart(2, '0'); const formattedMinutes = minutes.toString().padStart(2, '0'); return `${formattedHours}:${formattedMinutes}`; }, // 设置选择间区间 updateTimePickerOptions() { // 获取 playTimeSliderMarks 的最后一个间点 const lastTimeValue = Math.max(...Object.keys(this.playTimeSliderMarks).map(Number)); const lastTimeText = this.playTimeSliderMarks[lastTimeValue]; // 转换为 "HH:mm:ss" 格式 const [hours, minutes] = lastTimeText.split(':'); const maxTime = `${hours.padStart(2, '0')}:${minutes.padStart(2, '0')}:00`; // 设置间选择器的可选范围 this.timePickerOptions = { selectableRange: `00:00:00 - ${maxTime}` }; }, // 间轴确认 userChange(time) { if (!time || !this.player) return; // 未选择间或播放器未初始化则退出 // 1. 将选择的间(如 "01:02:00")转换为秒数 const seconds = this.timeToSeconds(time); // 2. 校验间是否在视频总长范围内(避免超出视频长度) if (seconds > this.videoDuration) { this.$message.warning("选择的间超出视频长"); return; } // 3. 调用 XGPlayer 的 API 跳转间并播放 this.player.currentTime = seconds; // 跳转至指定秒数 this.player.play(); // 开始播放 }, // 辅助方法:将 "HH:mm:ss" 转换为总秒数 timeToSeconds(timeStr) { const [hours, minutes, seconds] = timeStr.split(":").map(Number); return hours * 3600 + minutes * 60 + (seconds || 0); // 处理秒数为0的情况 }, // 处理播放进度更新:判断剩余5分钟和结束状态 handleProgressUpdate() { if (!this.videoDuration) return; const currentTime = Math.floor(this.player.currentTime); const remainTime = this.videoDuration - currentTime; // 剩余5分钟触发 if (remainTime <= 300 && !this.isRemain5MinTriggered) { this.isRemain5MinTriggered = true; this.dateChange() // if(!this.checkUrlsEqual(this.videoUrl, this.url2)) { // this.videoUrl = this.url2 // if (this.player) this.player.src = this.url2; // } } }, // 播放结束触发的逻辑(示例) handleVideoEnded() { if (!this.isFetchingNewVideo) { this.isFetchingNewVideo = true; // 标记已触发 this.dateChange() } // if(!this.checkUrlsEqual(this.videoUrl, this.url3)) { // this.videoUrl = this.url3 // if (this.player) this.player.src = this.url3; // } }, // 选择间播放 dateChange(e) { // this.videoUrl = 'https://linggan-xinlian.oss-cn-beijing.aliyuncs.com/41010500002000000003_41010500001320000311/replay.m3u8?x-oss-process=hls%2Fsign&x-oss-date=20250704T084918Z&x-oss-expires=108000&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5tPNiqn8Gwv4thoKTCPT%2F20250704%2Fcn-beijing%2Foss%2Faliyun_v4_request&x-oss-signature=030d365a30d6a0b92c73d34d3a1eb4d9dc98eccc0e7cc54a83b285b3dcaeceed' // return if (!this.chooseDate) { return; } let startTime = null let endTime = null if (e) { startTime = this.timeStamp(e[0]) endTime = this.timeStamp(e[1]); return console.log(startTime, endTime); } else { startTime = this.timeStamp(this.chooseDate + " 00:00:00") endTime = this.timeStamp(this.chooseDate + " 23:59:59"); } this.recordsLoading = true; const params = { // deviceId: this.$route.params.deviceId, // channelId: this.$route.params.channelId, id: this.$route.params.id, startTime: startTime, endTime: endTime } this.$axios({ method: 'post', url: '/api/device/query/getReplayUrl', data: params }).then((res) => { this.recordsLoading = false; this.isFetchingNewVideo = false; // 重置请求状态 if (res.data.code === 0) { this.videoUrl = 'https://linggan-xinlian.oss-cn-beijing.aliyuncs.com/41010500002000000003_41010500001320000311/replay.m3u8?x-oss-process=hls%2Fsign&x-oss-date=20250704T084918Z&x-oss-expires=108000&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5tPNiqn8Gwv4thoKTCPT%2F20250704%2Fcn-beijing%2Foss%2Faliyun_v4_request&x-oss-signature=030d365a30d6a0b92c73d34d3a1eb4d9dc98eccc0e7cc54a83b285b3dcaeceed' if (this.player) { this.player.src = this.videoUrl; this.player.play(); console.log('999') } // const newUrl = res.data.data.data // if (this.player) { // this.videoUrl = newUrl; // 更新数据 // this.player.src = newUrl; // 关键步骤:通知播放器更新 // this.player.load(); // 重新加载视频 // } // const newVideoUrl = res.data.data.data || ''; // if (!newVideoUrl) return; // // 赋值新视频地址 // if(!this.checkUrlsEqual(this.videoUrl, newVideoUrl)) { // this.videoUrl = res.data.data.data // console.log(this.videoUrl) // // if (this.player) this.player.src = this.videoUrl; // } // 加载新视频并续播 // this.loadNewVideoAndContinue(); } else { this.$message.error(res.data.msg); } }).catch((e) => { this.videoUrl = ''; this.recordsLoading = false; this.$message({ showClose: true, message: '暂无视频资源!', type: "error", }); }); }, // 5. 优化续播方法,接收播放状态参数 loadNewVideoAndContinue(wasPlaying) { if (!this.player) { this.initPlayer(); return; } // 清除旧监听 this.player.off('loadedmetadata', this.handleNewVideoLoaded); const self = this; // 新视频元数据加载完成回调 this.handleNewVideoLoaded = function () { const newDuration = Math.floor(self.player.duration); console.log(`新视频实际长: ${newDuration}s`); // 验证新视频确实更长 if (newDuration > self.lastDuration) { // 计算续播位置(不超过新视频长) const continuePosition = Math.min(self.lastPosition, newDuration); console.log(`续播位置: ${continuePosition}s`); // 跳转到续播位置 self.player.currentTime = continuePosition; // 恢复播放状态 if (wasPlaying) self.player.play(); // 更新滑块和长信息 self.videoDuration = newDuration; self.sliderMax = newDuration; self.generateTimeMarks(); } else { // 实际长未超过,直接播放新视频 if (wasPlaying) self.player.play(); } }; // 监听新视频元数据加载 this.player.on('loadedmetadata', this.handleNewVideoLoaded); // 加载新视频 this.player.load(); }, // 判断两个url是否相等 checkUrlsEqual(val1, val2) { return val1 === val2; }, // 继续播放 gbPlay() { if (!this.player) return; // 如果有间区间限制,确保从开始间播放 if (this.startTime && this.player.currentTime < this.startTime) { this.player.currentTime = this.startTime; } this.player.play(); }, // 结束播放按钮 gbPause() { if (this.player) { this.player.pause(); } }, // 间线宽度 getDataWidth(item) { let timeForFile = this.getTimeForFile(item); let result = (timeForFile[2]) / ((this.sliderMax - this.sliderMIn) * 1000) return result * 100 }, getDataLeft(item) { let timeForFile = this.getTimeForFile(item); let differenceTime = timeForFile[0].getTime() - new Date(this.chooseDate + " 00:00:00").getTime() return parseFloat((differenceTime - this.sliderMIn * 1000) / ((this.sliderMax - this.sliderMIn) * 1000)) * 100; }, playTimeChange(val) { if (!this.player) { this.$message.warning("播放器未初始化"); return; } // 校验秒数是否在视频长范围内 if (val > this.sliderMax) { this.$message.warning("目标间超出视频总长"); return; } // 跳转并播放 this.player.currentTime = val; // 设置播放器当前间(秒) this.player.play(); // 开始播放 }, getTimeForFile(file) { let startTime = new Date(file.startTime); let endTime = new Date(file.endTime); return [startTime, endTime, endTime.getTime() - startTime.getTime()]; }, // 间 playTimeFormat(val) { let h = parseInt(val / 3600); let m = parseInt((val - h * 3600) / 60); let s = parseInt(val - h * 3600 - m * 60); let hStr = h; let mStr = m; let sStr = s; if (h < 10) { hStr = "0" + hStr; } if (m < 10) { mStr = "0" + mStr; s } if (s < 10) { sStr = "0" + sStr; } return hStr + ":" + mStr + ":" + sStr }, // 返回上级 goBack() { window.history.go(-1); }, timeStamp(time) { // return Date.parse(time); const date = new Date(time); return Math.floor(date.getTime() / 1000); } }, // 组件销毁移除事件监听(避免内存泄漏) beforeDestroy() { if (this.player) { this.player.off('timeupdate', this.handleProgressUpdate); this.player.off('ended', this.handleVideoEnded); this.player.off('loadedmetadata', this.handleNewVideoLoaded); // 新增清理 } } }; </script> <style scoped> .playBox { min-width: 600px; max-width: 1200px; height: 70vh; width: 100%; overflow: hidden; /* 新增:裁剪溢出内容 */ border-radius: 8px; /* 直接给父容器设置圆角 */ } :deep(#videoPlay) { width: 100%; height: 100%; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } :deep(.playBox .xgplayer-error-tips) { display: none; } .el-slider__runway { background-color: rgba(206, 206, 206, 0.47) !important; } .el-slider__bar { background-color: rgba(153, 153, 153, 0) !important; } .playtime-slider { position: relative; z-index: 100; } .data-picker-true:after { content: ""; position: absolute; width: 4px; height: 4px; background-color: #606060; border-radius: 4px; left: 45%; top: 74%; } .slider-val-box { height: 6px; position: relative; top: -22px; } .slider-val { height: 6px; background-color: #007CFF; position: absolute; } .record-list-box-box { width: 290px; float: left; } .search-input-button { display: flex; justify-content: space-between; gap: 20px; } .record-list-no-val { position: absolute; color: #9f9f9f; top: 50%; left: 110px; } .loading-anim-box { position: absolute; top: 42%; left: 90px; } .player-option-box { padding: 0 20px; margin-top: 20px; } </style> 只要请求了视频地址,播放就失败,不请求前赋值可以播放
07-05
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值