<!-- 视频上传组件 -->
<template>
    <div style="position: relative; width: 360px; height: 220px;">
        <el-upload :data="uploadData" :disabled="uploading || transcoding" :action="uploadUrl" drag :before-upload="customReq" accept=".mp4" :show-file-list="false">
            <!-- 上传提示 -->
            <i v-show="!transcoding && !videoDone && !uploading" class="el-icon-upload"></i>
            <div v-show="!transcoding && !videoDone && !uploading" class="el-upload__text">将MP4文件拖到此处，或<em>点击上传</em></div>
            <div v-if="!transcoding && !videoDone && !uploading" style="text-align: center;" class="el-upload__tip" slot="tip">只能上传mp4文件</div>
            <div v-if="transcoding" style="text-align: center;" class="el-upload__tip" slot="tip">视频后台转码中，请稍后...</div>

            <!-- 进度条 -->
            <div v-show="!transcoding && !videoDone && uploading"
                style="position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);">
                <el-progress type="circle" :percentage="uploadPercentage" :status="uploadStatus"></el-progress>
                <div>{{ uploadSpeed }}</div>
            </div>

            <!-- 预览视频 -->
            <div v-show="!transcoding && videoDone">
                <video ref="refVideo" width="360px" height="180px"  controls crossOrigin="anonymous">
                    <source :src="`${videoUrl}`" type="video/mp4">
                    Your browser does not support the video tag.
                </video>
            </div>

            <!--  转换动画  -->
            <div v-if="transcoding">
                <div class="mesh-loader">
                    <div class="set-one">
                        <div class="circle"></div>
                        <div class="circle"></div>
                    </div>
                    <div class="set-two">
                        <div class="circle"></div>
                        <div class="circle"></div>
                    </div>
                </div>
            </div>
        </el-upload>

        <!-- 截图 下载 删除按钮 -->
        <div v-if="videoDone" class="buttons">
            <el-button type="primary" size="mini" icon="el-icon-camera" @click="captureVideo"></el-button>
            <el-button type="primary" size="mini" icon="el-icon-download" @click="download"></el-button>
            <el-button type="danger" size="mini" icon="el-icon-delete" @click="deleteVideo"></el-button>
        </div>
    </div>
</template>

<script>
import cryptoJs from 'crypto-js';
import axios from 'axios';
import SparkMD5 from 'spark-md5';

export default {
    name: 'VideoUploader',
    props: {
        value: {
            type: String,
            default: ''
        },
    },
    watch: {
        value: function (val, oldVal) {
            // 监测父组件的更新
            if(val) {
                // 查看是否资源已经存在
                this.httpHeadCall(val, async (exist) => {
                    if (exist) {
                        // 存在，直接标记视频完成
                        this.videoDone = true
                    } else {
                        // 如果不存在则默认是正在后台转码状态
                        this.transcoding = true;
                        await this.checkResourceAvaliable();
                        this.$message({
                            type: 'success',
                            message: '转换完成'
                        });
                        this.videoDone = true;
                        this.transcoding = false;

                        // 刷新视频源
                        this.$refs.refVideo.load()
                    }
                })
            }

            this.videoUrl = val
        },
        videoUrl: function() {
            this.$refs.refVideo.load()
        }
    },
    data() {
        return {
            // 上传地址
            uploadUrl: "",
            // 上传附带的DATA
            uploadData: "",

            // 上传中标记位
            uploading: false,
            // 转码中
            transcoding: false,
            // 视频是否上传完成
            videoDone: false,

            // 上传进度
            uploadPercentage: 0,
            // 上传进度条状态 null success
            uploadStatus: null,
            // 上传速度
            uploadSpeed: "0KB/s",
            // 上传开始时间
            startTime: null,
            // 上传结束时间
            endTime: null,
            // 已经上传的文件大小
            loaded: 0,
            // 视频地址
            videoUrl: "",

            // 定时器ID
            setIntervalIds: [],
            setTimeoutIds: [],

        }
    },

    created() {
        // 获取上传地址
        this.uploadUrl = window.courseware.uploadVideoUrl();
        this.uploadData = window.courseware.uploadData();
        window['air'].getUploadVideoCallback = (url, data) => {
            this.uploadUrl = url;
            this.uploadData = data;
        };
    },

    mounted() {
        // 添加点击事件监听，防止点击视频画面自动播放。（点击视频画面可以重新上传视频）
        this.$refs.refVideo.addEventListener('click', () => {
            const stop = () => {
                this.$refs.refVideo.pause();
                this.$refs.refVideo.removeEventListener("play", stop)
            }
            this.$refs.refVideo.addEventListener("play", stop)
        });
    },

    methods: {
        // 解析文件名
        parseFileName(fileName) {
            const lastDotIndex = fileName.lastIndexOf('.');
            const name = fileName.substring(0, lastDotIndex);
            const ext = fileName.substring(lastDotIndex);
            return { name, ext };
        },

        // 重置上传变量
        resetUploadVars() {
            this.uploadPercentage = 0;
            this.uploadStatus = null;
            this.uploadSpeed = "0KB/s";
            this.startTime = null;
            this.endTime = null;
            this.loaded = 0;
        },

        // 发送HEAD请求 测试资源是否存在
        httpHeadCall(requsetUrl, callback) {
            let xhr = new XMLHttpRequest();
            try {
                xhr.onreadystatechange = () => {
                    try {
                        if (xhr.readyState == 4) {
                            if ((xhr.status >= 200 && xhr.status < 400)) {
                                callback(true);
                            } else {
                                callback(false);
                            }
                        }
                    } catch (e) {
                        callback(false);
                    }
                };
                xhr.open("HEAD", requsetUrl, true);
                xhr.send();
                xhr.timeout = 15000;
                xhr.onerror = (e) => {
                    callback(false);
                };
                xhr.ontimeout = (e) => {
                    callback(false);
                };
            } catch (e) {
                callback(false);
            }
        },

        // 上传文件
        async customReq(item) {
            const { name, ext } = this.parseFileName(item.name)
            if (ext !== '.mp4') {
                this.$message({
                    type: 'error',
                    message: '只能上传mp4文件'
                });
                return false
            }

            // 获取文件MD5
            const fileName = await this.getFileMD5(item);

            const protocolStr = document.location.protocol;
            const baseCDN = `${protocolStr}//teach.cdn.ireadabc.com`;
            const baseAPI = `${protocolStr}//openapi.iteachabc.com`;

            // 如果当前视频和新上传的视频是同一个，则不变。
            if(this.videoUrl == `${baseCDN}/${fileName}_h.mp4`) {
                this.$refs.refVideo.pause();
                this.$refs.refVideo.currentTime = 0;
                this.$message({
                    type: 'success',
                    message: '极速上传完成'
                })
                return
            }

            // 重置上传变量，并设置状态为上传中
            this.resetUploadVars();
            this.videoUrl = "";
            this.uploading = true;
            this.transcoding = false;
            this.videoDone = false;

            // 测试文件是否在服务上存在
            this.httpHeadCall(`${baseCDN}/${fileName}_h.mp4`, (exit) => {
                if(exit) {
                    // 文件存在，极速上传完成。并标记为完成状态。
                    this.uploadStatus = "success";
                    this.videoUrl = `${baseCDN}/${fileName}_h.mp4`;
                    this.$emit("input", this.videoUrl);
                    this.$emit('change', { url: this.videoUrl, file: item });
                    this.$emit('success', { url: this.videoUrl, file: item });
                    this.uploading = false;

                    this.$message({
                        type: 'success',
                        message: '极速上传完成'
                    })
                    return
                }

                // 开始上传到OSS
                axios.get(`${baseAPI}/oss/timestamp`, {}).then(tmps => {
                    const sign = cryptoJs.MD5(tmps + "iplayABC2019").toString();

                    axios.get(`${baseAPI}/oss/signature`, { sign, tmps }).then((res) => {
                        const serve = res.data.data;
                        const formData = new FormData();
                        formData.append('key', fileName + `.mp4`);
                        formData.append('bucket', `iplayabc-worker-queue-videolmh`);
                        formData.append('OSSAccessKeyId', serve.OSSAccessKeyId);
                        formData.append('policy', serve.policy);
                        formData.append('Signature', serve.signature);
                        formData.append('success_action_status', '200');
                        formData.append('file', item);

                        item.action = `${protocolStr}//iplayabc-worker-queue-videolmh.oss-cn-beijing.aliyuncs.com`;

                        // 标记开始时间
                        this.startTime = new Date().getTime();
                        this.uploadStatus = null
                        axios({
                            method: 'post',
                            url: item.action,
                            headers: { 'Access-Control-Allow-Origin': '*' },
                            onUploadProgress: (progressEvent) => {
                                // 更新上传进度
                                this.uploadPercentage = Math.ceil(progressEvent.progress * 100);
                                // 计算上传速率
                                this.endTime = new Date().getTime();
                                const timeDiff = this.endTime - this.startTime;
                                this.startTime = new Date().getTime();
                                if (timeDiff === 0) return;

                                const sent = progressEvent.loaded - this.loaded;
                                this.loaded = progressEvent.loaded;
                                const speed = (sent / timeDiff) * 1000; // Convert to KB/s
                                const spNum = Math.round(speed / 1024);
                                if (spNum > 1024) {
                                    this.uploadSpeed = `${(spNum / 1024).toFixed(2)}MB/s`;
                                } else {
                                    this.uploadSpeed = `${spNum}KB/s`;
                                }

                                this.$emit('onProgress', progressEvent);
                            },
                            data: formData
                        }).then(async event => {
                            // 上传完成，延迟隐藏状态
                            const tid = setTimeout(() => {
                                this.resetUploadVars();
                                this.uploading = false;
                                this.transcoding = true;
                                this.videoDone = false;

                                const idIndex = this.setTimeoutIds.indexOf(tid);
                                if (idIndex > -1) {
                                    this.setTimeoutIds.splice(idIndex, 1);
                                }
                            }, 1000);

                            // 编辑为成功状态
                            this.uploadStatus = "success";
                            this.videoUrl = `${baseCDN}/${fileName}_h.mp4`;

                            this.$emit("input", this.videoUrl);
                            this.$emit('change', { url: this.videoUrl, file: item });
                            this.$emit('success', { url: this.videoUrl, file: item });

                            this.setTimeoutIds.push(tid);
                            this.uploading = false;

                            this.$message({
                                type: 'info',
                                message: '上传成功，开始转码...'
                            });
                        }).catch(err => {
                            // 报错
                            const tid = setTimeout(() => {
                                this.uploading = false;
                                this.transcoding = false;
                                this.videoDone = false;
                                const idIndex = this.setTimeoutIds.indexOf(tid);
                                if (idIndex > -1) {
                                    this.setTimeoutIds.splice(idIndex, 1);
                                }
                            }, 1000);
                            this.uploadStatus = "error";
                            this.$emit('error', err);
                            this.setTimeoutIds.push(tid);
                        })
                    });
                });
            })
            

            return false
        },

        // 检查资源是否可用
        checkResourceAvaliable() {
            return new Promise(resovle => {
                let intervalId = null;
                intervalId = setInterval(() => {
                    this.httpHeadCall(this.videoUrl, (isSuccess) => {
                        if (isSuccess) {
                            if (intervalId) {
                                const index = this.setIntervalIds.indexOf(intervalId)
                                clearInterval(intervalId)
                                this.setIntervalIds.splice(index, 1)
                            }
                            resovle(true)
                        }
                    });
                }, 800)
                this.setIntervalIds.push(intervalId)
            })
        },

        async download() {
            const url = this.videoUrl
            const fileName = url.split('/').pop()
            const response = await fetch(url) //发送GET请求
            if (response.ok) {
                const blob = await response.blob() //获取Blob对象
                const url = window.URL.createObjectURL(blob) //创建Blob对象的URL
                const link = document.createElement('a') //创建一个新的<a>元素
                link.href = url //设置<a>元素的href属性为Blob对象的URL
                link.download = fileName //设置下载文件的文件名
                document.body.appendChild(link) //将<a>元素添加到文档中
                link.click() //模拟点击<a>元素以触发文件下载
                this.$message({
                    type: 'success',
                    message: '下载完成'
                });
            } else {
                //处理API响应失败的情况，例如显示错误消息
                this.$message({
                    type: 'error',
                    message: '下载失败'
                });
            }
        },

        captureVideo() {
            const fullFileName = this.videoUrl.split('/').pop();
            const { name, ext } = this.parseFileName(fullFileName)
            const video = this.$refs.refVideo;

            const cvs = document.createElement('canvas');
            const ctx = cvs.getContext("2d");
            cvs.width = video.videoWidth;
            cvs.height = video.videoHeight;
            ctx.drawImage(video, 0, 0, cvs.width, cvs.height);
            const screenshotDataUrl = cvs.toDataURL('image/png');

            const link = document.createElement('a') //创建一个新的<a>元素
            link.href = screenshotDataUrl //设置<a>元素的href属性为Blob对象的URL
            link.download =  name + "_" + Math.floor(video.currentTime * 1000) + ".png"//设置下载文件的文件名
            document.body.appendChild(link) //将<a>元素添加到文档中
            link.click() //模拟点击<a>元素以触发文件下载
            this.$message({
                type: 'success',
                message: '截图成功'
            });
        },

        deleteVideo() {
            this.$confirm('确定要删除吗？', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(() => {
                this.videoUrl = "";
                this.videoDone = false;
                this.$emit("input", "");
                this.$emit('change', { url: this.videoUrl, file: null });
            }).catch(() => {
                this.$message({
                    type: 'info',
                    message: '已取消删除'
                });
            });
        },

        // 计算文件MD5
        getFileMD5(file) {
            return new Promise(resovle => {
                let fileReader = new FileReader();
                let Spark = new SparkMD5.ArrayBuffer();
                fileReader.readAsArrayBuffer(file);
                fileReader.onload = function (e) {
                    Spark.append(e.target.result);
                    let md5 = Spark.end();
                    resovle(md5);
                }
            })
        },
    },

    // 组件销毁时清除定时器
    beforeDestroy() {
        this.setTimeoutIds.forEach(tid => {
            clearTimeout(tid);
        });
        this.setIntervalIds.forEach(iid => {
            clearInterval(iid)
        })
    }
}
</script>

<style scoped>
.buttons {
    text-align: center
}

.buttons .el-button {
    padding: 4px 12px;
    font-size: 18px;
}

/* MESH LOADER */

.mesh-loader {
    overflow: hidden;
    height: inherit;
    width: inherit;
}

.mesh-loader .circle {
    width: 10px;
    height: 10px;
    position: absolute;
    background: #f44336;
    border-radius: 50%;
    margin: -5px;
    -webkit-animation: mesh 3s ease-in-out infinite -1.5s;
    animation: mesh 3s ease-in-out infinite -1.5s;
}

.mesh-loader>div .circle:last-child {
    -webkit-animation-delay: 0s;
    animation-delay: 0s;
}

.mesh-loader>div {
    position: absolute;
    top: 50%;
    left: 50%;
}

.mesh-loader>div:last-child {
    -webkit-transform: rotate(90deg);
    -ms-transform: rotate(90deg);
    transform: rotate(90deg);
}

@keyframes mesh {
    0% {
        -webkit-transform-origin: 50% -100%;
        transform-origin: 50% -100%;
        -webkit-transform: rotate(0);
        transform: rotate(0);
    }

    50% {
        -webkit-transform-origin: 50% -100%;
        transform-origin: 50% -100%;
        -webkit-transform: rotate(360deg);
        transform: rotate(360deg);
    }

    50.1% {
        -webkit-transform-origin: 50% 200%;
        transform-origin: 50% 200%;
        -webkit-transform: rotate(0deg);
        transform: rotate(0deg);
    }

    100% {
        -webkit-transform-origin: 50% 200%;
        transform-origin: 50% 200%;
        -webkit-transform: rotate(360deg);
        transform: rotate(360deg);
    }
}
</style>