当前位置: 首页 > 工具软件 > CanJS > 使用案例 >

自定义视频播放控件样式_使用CanJS创建自定义视频播放器控件

尹钱青
2023-12-01

自定义视频播放控件样式

In this guide, you will learn how to create a custom video player using the <video> element and CanJS. The custom video player will:

在本指南中,您将学习如何使用<video>元素和CanJS创建自定义视频播放器。 自定义视频播放器将:

  • Have custom play and pause buttons.

    具有自定义播放和暂停按钮。
  • Show the current time and duration of the video.

    显示视频的当前时间和时长。
  • Have a <input type="range"> slider that can adjust the position of the video.

    有一个<input type="range">滑块可以调整视频的位置。

The final player looks like:

最终玩家如下所示:

The following sections are broken down into the following parts:

以下各节分为以下几部分:

  • The problem — A description of what the section is trying to accomplish.

    问题 -对本节要完成的工作的描述。

  • What you need to know — Browser or CanJS APIs that are useful for solving the problem.

    您需要知道的 -对解决问题有用的浏览器或CanJS API。

  • The solution — The solution to the problem.

    解决方案 —解决问题的方法。

建立 (Setup)

START THIS TUTORIAL BY Forking THE FOLLOWING CodePen:

通过分叉以下CodePen开始本教程

Click the Edit in CodePen button. The CodePen will open in a new window. Click the Fork button.

单击Edit in CodePen按钮。 CodePen将在新窗口中打开。 单击Fork按钮。

This CodePen:

该CodePen:

  • Creates a <video> element that loads a video. Right click and select “Show controls” to see the video’s controls.

    创建一个<video>元素来加载视频。 右键单击并选择“显示控件”以查看视频的控件

  • Loads CanJS's custom element library: Component.

    加载CanJS的自定义元素库: Component

问题 (The problem)

In this section, we will:

在本节中,我们将:

  • Create a custom <video-player> element that takes a src attribute and creates a <video> element within itself. We should be able to create the video like:

    创建一个具有src属性的自定义<video-player>元素,并在其内部创建一个<video>元素。 我们应该能够像这样创建视频:

    <video-player src:raw="http://bit.ly/can-tom-n-jerry">
    </video-player>
  • The embedded <video> element should have the native controls enabled.

    嵌入的<video>元素应启用本机控件。

When complete, the result will look exactly the same as the player when you started. The only difference is that we will be using a custom <video-player> element in the HTML tab instead of the native <video> element.

完成后,结果将与开始时的播放器完全相同。 唯一的区别是,我们将在HTML选项卡中使用自定义的<video-player>元素,而不是本机的<video>元素。

你需要知道的 (What you need to know)

To set up a basic CanJS application (or widget), you define a custom element in JavaScript and use the custom element in your page’s HTML.

要设置基本的CanJS应用程序(或小部件),请在JavaScript中定义一个自定义元素,然后在页面的HTML使用该自定义元素。

To define a custom element, extend Component with a tag that matches the name of your custom element. For example:

要定义自定义元素,请使用与您的自定义元素名称匹配的标签扩展Component 。 例如:

Component.extend({
    tag: "video-player"
})

Then you can use this tag in your HTML page:

然后,您可以在HTML页面中使用此标记:

<video-player></video-player>

But this doesn’t do anything ... yet. Components add their own HTML through their view property:

但这还没有任何作用...。 组件通过其view属性添加自己HTML:

Component.extend({
    tag: "video-player",
    view: `<h2>I am a player!</h2>`
});

A component’s view is rendered with its ViewModel. For example, we can make a <video> display "http://bit.ly/can-tom-n-jerry" by defining a src property on the ViewModel and using it in the view like:

组件的视图使用其ViewModel呈现。 例如,我们可以通过在ViewModel上定义src属性并在如下视图中使用它来使<video>显示"http://bit.ly/can-tom-n-jerry"

Component.extend({
    tag: "video-player",
    view: `
        <video>
            <source src="{{src}}"/>
        </video>
    `,
    ViewModel: {
        src: {type: "string", default: "http://bit.ly/can-tom-n-jerry"}
    }
});

But we want the <video-player> to take a src attribute value itself and use that for the <source>’s src. For example, if we wanted the video to play "http://dl3.webmfiles.org/big-buck-bunny_trailer.webm" instead of "http://bit.ly/can-tom-n-jerry", we would:

但是我们希望<video-player>本身采用src属性值,并将其用于<source>src 。 例如,如果我们希望视频播放"http://dl3.webmfiles.org/big-buck-bunny_trailer.webm"而不是"http://bit.ly/can-tom-n-jerry" ,那么我们将:

  1. Update <video-player> to pass "http://dl3.webmfiles.org/big-buck-bunny_trailer.webm" with toChild:raw:

    更新<video-player>以通过toChild:raw传递"http://dl3.webmfiles.org/big-buck-bunny_trailer.webm"

    <video-player src:raw="http://dl3.webmfiles.org/big-buck-bunny_trailer.webm"/>
  2. Update the ViewModel to define a src property like:

    更新ViewModel以定义src属性,例如:

    Component.extend({
        tag: "video-player",
        view: `
            <video>
                <source src="{{src}}"/> {{!}}
            </video>
        `,
        ViewModel: {
            src: "string"
        }
    });

Finally, to have a <video> element show the native controls, add a controls attribute like:

最后,要使<video>元素显示本控件,请添加如下controls属性:

<video controls>

解决方案 (The solution)

Update the JS tab to:

JS标签更新为:

import {Component} from "//unpkg.com/can@5/core.mjs";

Component.extend({                        //
    tag: "video-player",                  //
    view: `                              {{!}}
        <video controls>                 {{!}}
            <source src="{{src}}"/>      {{!}}
        </video>                         {{!}}
    `,                                    //
    ViewModel: {                          //
        src: "string",                    //
    }                                     //
});                                       //

Update the HTML to:

HTML更新为:

<video-player src:raw="http://bit.ly/can-tom-n-jerry"></video-player>   <!---->

在播放和暂停视频时更改播放/暂停按钮 (Make play / pause button change as video is played and paused)

问题 (The problem)

When the video is played or paused using the native controls, we want to change the content of a <button> to say “Play” or “Pause”.

使用本机控件播放或暂停视频时,我们希望将<button>的内容更改为“播放”“暂停”

When the video is played, the button should say “Pause”. When the video is paused, the button should say “Play”.

播放视频时,按钮应显示“暂停” 。 视频暂停时,按钮应显示“播放”

We want the button to be within a <div> after the video element like:

我们希望按钮位于视频元素之后的<div>内,例如:

</video>
<div>
    <button>Play</button>
</div>

你需要知道的 (What you need to know)

  • To change the HTML content of the page, use {{#if(expression)}} and {{else}} like:

    要更改页面HTML内容,请使用{{#if(expression)}}{{else}},如下所示:

    <button>{{#if(playing)}} Pause {{else}} Play {{/if}}</button>
  • The view responds to values in the ViewModel. To create a boolean value in the ViewModel do:

    视图响应ViewModel中的值。 要在ViewModel中创建boolean值,请执行以下操作:

    ViewModel: {
        // ...
        playing: "boolean",
    }
  • Methods can be used to change the ViewModel. The following might create methods that change the playing value:

    方法可用于更改ViewModel 。 以下可能会创建更改playing价值的方法:

    ViewModel: {
        // ...
        play() {
            this.playing = true;
        },
        pause() {
            this.playing = false;
        },
    }
  • You can listen to events on the DOM with on:event. For example, the following might listen to a click on a <div> and call doSomething():

    您可以使用on:event监听DOM 上的事件 。 例如,以下内容可能会监听<div>的点击并调用doSomething()

    <div on:click="doSomething()">

    <video> elements have a variety of useful events, including play and pause events that are emitted when the video is played or paused.

    <video>元素具有各种有用的事件 ,包括在播放或暂停视频时发出的播放暂停事件。

解决方案 (The solution)

Update the JavaScript tab to:

JavaScript标签更新为:

import {Component} from "//unpkg.com/can@5/core.mjs";

Component.extend({
    tag: "video-player",
    view: `
        <video controls
            on:play="play()"                                      {{!}}
            on:pause="pause()">                                   {{!}}
            <source src="{{src}}"/>
        </video>
        <div>                                                     {{!}}
            <button>                                              {{!}}
                {{#if(playing)}} Pause {{else}} Play {{/if}}      {{!}}
            </button>                                             {{!}}
        </div>                                                    {{!}}
    `,
    ViewModel: {
        src: "string",
        playing: "boolean",                                        //

        play() {                                                   //
            this.playing = true;                                   //
        },                                                         //
        pause() {                                                  //
            this.playing = false;                                  //
        },                                                         //
    }
});

使单击“播放/暂停”按钮播放或暂停视频 (Make clicking the play/pause button play or pause the video)

问题 (The problem)

When the play/pause <button> we created in the previous section is clicked, we want to either play or pause the video.

当单击上一节中创建的播放/暂停 <button> ,我们要播放或暂停视频。

你需要知道的 (What you need to know)

CanJS prefers to manage the state of your application in ViewModel. The <video> player has state, such as if the video is playing. When the play/pause button is clicked, we want to update the state of the ViewModel and have the ViewModel update the state of the video player as a side effect.

CanJS倾向于在ViewModel中管理应用程序的状态。 <video>播放器具有状态,例如正在playing视频。 单击“ 播放/暂停”按钮时,我们要更新ViewModel的状态,并让ViewModel更新视频播放器的状态作为副作用。

What this means is that instead of something like:

这意味着它不是这样的:

togglePlay() {
    if ( videoElement.paused ) {
        videoElement.play()
    } else {
        videoElement.pause()
    }
}

We update the state like:

我们将状态更新为:

togglePlay() {
    this.playing = !this.playing;
}

And listen to when playing changes and update the video element like:

并在playing更改时收听并更新video元素,例如:

viewModel.listenTo("playing", function(event, isPlaying) {
    if ( isPlaying ) {
        videoElement.play()
    } else {
        videoElement.pause()
    }
})

This means that you need to:

这意味着您需要:

  1. Listen to when the <button> is clicked and call a ViewModel method that updates the playing state.

    收听单击<button>时的声音,并调用更新playing状态的ViewModel方法。

  2. Listen to when the playing state changes and update the state of the <video> element.

    收听playing状态更改时的playing并更新<video>元素的状态。

You already know everything you need to know for step #1. (Have the button call a togglePlay method with on:click="togglePlay()" and make the togglePlay() method toggle the state of the playing property.)

你已经知道你需要知道的步骤#1的一切。 (让按钮使用on:click="togglePlay()"调用togglePlay方法,并使togglePlay()方法切换playing属性的状态。)

For step #2, you need to use the connectedCallback lifecycle hook. This hook gives you access to the component’s element and is a good place to do side-effects. Its use looks like this:

对于步骤2 ,您需要使用connectedCallback生命周期挂钩。 该挂钩使您可以访问组件的元素,并且是处理副作用的好地方。 它的用法如下所示:

ViewModel: {
    // ...
    connectedCallback(element) {
        // perform mischief
    }
}

connectedCallback gets called once the component’s element is in the page. You can use listenTo to listen to changes in the ViewModel’s properties and perform side-effects. The following listens to when playing changes:

一旦组件的element在页面中,就会调用connectedCallback 。 您可以使用listenTo来侦听ViewModel的属性中的更改并执行副作用。 playing更改时,以下内容会监听:

ViewModel: {
    // ...
    connectedCallback(element) {
        this.listenTo("playing", function(event, isPlaying) {

        })
    }
}

Use querySelector to get the <video> element from the <video-player> like:

使用querySelector<video-player>获取<video>元素,例如:

element.querySelector("video")

<video> elements have a .play() and .pause() methods that can start and stop a video.

<video>元素具有可以启动和停止视频的.play().pause()方法。

解决方案 (The solution)

Update the JavaScript tab to:

JavaScript标签更新为:

import {Component} from "//unpkg.com/can@5/core.mjs";

Component.extend({
    tag: "video-player",
    view: `
        <video controls
            on:play="play()"
            on:pause="pause()">
            <source src="{{src}}"/>
        </video>
        <div>
            <button on:click="togglePlay()">                        {{!}}
                {{#if(playing)}} Pause {{else}} Play {{/if}}
            </button>
        </div>
    `,
    ViewModel: {
        src: "string",
        playing: "boolean",

        play() {
            this.playing = true;
        },
        pause() {
            this.playing = false;
        },
        togglePlay() {                                               //
            this.playing = !this.playing;                            //
        },                                                           //

        connectedCallback(element) {                                 //
            this.listenTo("playing", function(event, isPlaying) {    //
                if (isPlaying) {                                     //
                    element.querySelector("video").play();           //
                } else {                                             //
                    element.querySelector("video").pause();          //
                }                                                    //
            });                                                      //
        }                                                            //
    }
});

显示当前时间和持续时间 (Show current time and duration)

问题 (The problem)

Show the current time and duration of the video element. The time and duration should be formatted like: mm:SS. They should be presented within two spans like:

显示视频元素的当前时间和持续时间。 时间和持续时间的格式应为: mm:SS 。 它们应在两个范围内显示:

</button>
<span>1:22</span>
<span>2:45</span>

你需要知道的 (What you need to know)

  1. Methods can be used to format values in can-stache. For example, you can uppercase values like this:

    可以使用方法来格式化can-stache中的值。 例如,您可以像这样大写值:

    <span>{{upper(value)}}</span>

    With a method like:

    使用类似的方法:

    ViewModel: {
        // ...
        upper(value) {
            return value.toString().toUpperCase();
        }
    }

    The following can be used to format time:

    以下内容可用于格式化时间:

    formatTime(time) {
        if (time === null || time === undefined) {
            return "--";
        }
        const minutes = Math.floor(time / 60);
        let seconds = Math.floor(time - minutes * 60);
        if (seconds < 10) {
            seconds = "0" + seconds;
        }
        return minutes + ":" + seconds;
    }
  2. Time is given as a number. Use the following to create a number property on the ViewModel:

    时间以数字形式给出。 使用以下命令在ViewModel上创建一个number属性:

    ViewModel: {
        // ...
        duration: "number",
        currentTime: "number"
    }
  3. <video> elements emit a loadmetadata event when they know how long the video is. They also emit a timeupdate event when the video’s current play position changes.

    <video>元素在知道视频多长时间时会发出loadmetadata事件 。 当视频的当前播放位置更改时,它们还会发出timeupdate事件

    • videoElement.duration reads the duration of a video.videoElement.duration读取视频的时长。
    • videoElement.currentTime reads the current play position of a video.videoElement.currentTime读取视频的当前播放位置。
  4. You can get the element in an stache on:event binding with scope.element like:

    您可以使用scope.element下面这样on:event绑定中获取元素:

    <video on:timeupdate="updateTimes(scope.element)"/>

解决方案 (The solution)

Update the JavaScript tab to:

JavaScript标签更新为:

import {Component} from "//unpkg.com/can@5/core.mjs";

Component.extend({
    tag: "video-player",
    view: `
        <video controls
            on:play="play()"
            on:pause="pause()"
            on:timeupdate="updateTimes(scope.element)"           {{!}}
            on:loadedmetadata="updateTimes(scope.element)">      {{!}}
            <source src="{{src}}"/>
        </video>
        <div>
            <button on:click="togglePlay()">
                {{#if(playing)}} Pause {{else}} Play {{/if}}
            </button>
            <span>{{formatTime(currentTime)}}</span> /           {{!}}
            <span>{{formatTime(duration)}} </span>               {{!}}
        </div>
    `,
    ViewModel: {
        src: "string",
        playing: "boolean",
        duration: "number",                                       //
        currentTime: "number",                                    //

        updateTimes(videoElement) {                               //
            this.currentTime = videoElement.currentTime || 0;     //
            this.duration = videoElement.duration;                //
        },                                                        //
        formatTime(time) {                                        //
            if (time === null || time === undefined) {            //
                return "--";                                      //
            }                                                     //
            const minutes = Math.floor(time / 60);                //
            let seconds = Math.floor(time - minutes * 60);        //
            if (seconds < 10) {                                   //
                seconds = "0" + seconds;                          //
            }                                                     //
            return minutes + ":" + seconds;                       //
        },                                                        //
        play() {
            this.playing = true;
        },
        pause() {
            this.playing = false;
        },
        togglePlay() {
            this.playing = !this.playing;
        },

        connectedCallback(element) {
            this.listenTo("playing", function(event, isPlaying) {
                if (isPlaying) {
                    element.querySelector("video").play();
                } else {
                    element.querySelector("video").pause();
                }
            });
        }
    }
});

使范围在当前时间显示位置滑块 (Make range show position slider at current time)

问题 (The problem)

Create a <input type="range"/> element that changes its position as the video playing position changes.

创建一个<input type="range"/>元素,该元素随视频播放位置的变化而改变其位置。

The <input type="range"/> element should be after the <button> and before the currentTime span like:

<input type="range"/>元素应位于<button>currentTime跨度之前,例如:

</button>
<input type="range"/>
<span>{{formatTime(currentTime)}}</span> /

你需要知道的 (What you need to know)

  • The range input can have an initial value, max value, and step size specified like:

    范围输入可以指定一个初始值,最大值和步长,例如:

    <input type="range" value="0" max="1" step="any"/>
  • The range will have values from 0 to 1. We will need to translate the currentTime to a number between 0 and 1. We can do this with a computed getter property like:

    范围的值从0到1。我们需要将currentTime转换为0到1之间的数字。我们可以使用计算得到的getter属性来完成此操作,例如:

    ViewModel: {
        // ...
        get percentComplete() {
            return this.currentTime / this.duration;
        },
    }
  • Use key:from to update a value from a ViewModel property like:

    使用key:fromViewModel属性更新值,例如:

    <input value:from="percentComplete"/>

解决方案 (The solution)

Update the JavaScript tab to:

JavaScript标签更新为:

import {Component} from "//unpkg.com/can@5/core.mjs";

Component.extend({
    tag: "video-player",
    view: `
        <video controls
            on:play="play()"
            on:pause="pause()"
            on:timeupdate="updateTimes(scope.element)"
            on:loadedmetadata="updateTimes(scope.element)">
            <source src="{{src}}"/>
        </video>
        <div>
            <button on:click="togglePlay()">
                {{#if(playing)}} Pause {{else}} Play {{/if}}
            </button>
            <input type="range" value="0" max="1" step="any"        {{!}}
                    value:from="percentComplete"/>                  {{!}}
            <span>{{formatTime(currentTime)}}</span> /
            <span>{{formatTime(duration)}} </span>
        </div>
    `,
    ViewModel: {
        src: "string",
        playing: "boolean",
        duration: "number",
        currentTime: "number",

        get percentComplete() {                                      //
            return this.currentTime / this.duration;                 //
        },                                                           //

        updateTimes(videoElement) {
            this.currentTime = videoElement.currentTime || 0;
            this.duration = videoElement.duration;
        },
        formatTime(time) {
            if (time === null || time === undefined) {
                return "--";
            }
            const minutes = Math.floor(time / 60);
            let seconds = Math.floor(time - minutes * 60);
            if (seconds < 10) {
                seconds = "0" + seconds;
            }
            return minutes + ":" + seconds;
        },
        play() {
            this.playing = true;
        },
        pause() {
            this.playing = false;
        },
        togglePlay() {
            this.playing = !this.playing;
        },

        connectedCallback(element) {
            this.listenTo("playing", function(event, isPlaying) {
                if (isPlaying) {
                    element.querySelector("video").play();
                } else {
                    element.querySelector("video").pause();
                }
            });
        }
    }
});

使滑动范围更新当前时间 (Make sliding the range update the current time)

问题 (The problem)

In this section we will:

在本节中,我们将:

  • Remove the native controls from the video player. We don’t need them anymore!

    从视频播放器中删除本机控件。 我们不再需要它们了!
  • Make it so when a user moves the range slider, the video position updates.

    当用户移动范围滑块时,视频位置会更新。

你需要知道的 (What you need to know)

Similar to when we made the play/pause button play or pause the video, we will want to update the currentTime property and then listen to when currentTime changes and update the <video> element’s currentTime as a side-effect.

类似于使播放/暂停按钮播放或暂停视频时 ,我们将要更新currentTime属性,然后在currentTime变化时进行监听,并更新<video>元素的currentTime作为副作用

This time, we need to translate the sliders values between 0 and 1 to currentTime values. We can do this by creating a percentComplete setter that updates currentTime like:

这次,我们需要将0到1之间的滑块值转换为currentTime值。 为此,我们可以创建一个percentComplete 设置器来更新currentTime例如:

ViewModel: {
    // ...
    get percentComplete() {
        return this.currentTime / this.duration;
    },
    set percentComplete(newVal) {
        this.currentTime = newVal * this.duration;
    },
    // ...
}

Use key:bind to two-way bind a value to a ViewModel property:

使用key:bind双向绑定一个值到ViewModel属性:

<input value:bind="someViewModelProperty"/>

解决方案 (The solution)

Update the JavaScript tab to:

JavaScript标签更新为:

import {Component} from "//unpkg.com/can@5/core.mjs";

Component.extend({
    tag: "video-player",
    view: `
        <video                                                          {{!}}
            on:play="play()"
            on:pause="pause()"
            on:timeupdate="updateTimes(scope.element)"
            on:loadedmetadata="updateTimes(scope.element)">
            <source src="{{src}}"/>
        </video>
        <div>
            <button on:click="togglePlay()">
                {{#if(playing)}} Pause {{else}} Play {{/if}}
            </button>
            <input type="range" value="0" max="1" step="any"
                    value:bind="percentComplete"/>                      {{!}}
            <span>{{formatTime(currentTime)}}</span> /
            <span>{{formatTime(duration)}} </span>
        </div>
    `,
    ViewModel: {
        src: "string",
        playing: "boolean",
        duration: "number",
        currentTime: "number",

        get percentComplete() {
            return this.currentTime / this.duration;
        },
        set percentComplete(newVal) {                                    //
            this.currentTime = newVal * this.duration;                   //
        },                                                               //

        updateTimes(videoElement) {
            this.currentTime = videoElement.currentTime || 0;
            this.duration = videoElement.duration;
        },
        formatTime(time) {
            if (time === null || time === undefined) {
                return "--";
            }
            const minutes = Math.floor(time / 60);
            let seconds = Math.floor(time - minutes * 60);
            if (seconds < 10) {
                seconds = "0" + seconds;
            }
            return minutes + ":" + seconds;
        },
        play() {
            this.playing = true;
        },
        pause() {
            this.playing = false;
        },
        togglePlay() {
            this.playing = !this.playing;
        },

        connectedCallback(element) {
            this.listenTo("playing", function(event, isPlaying) {
                if (isPlaying) {
                    element.querySelector("video").play();
                } else {
                    element.querySelector("video").pause();
                }
            });
            this.listenTo("currentTime", function(event, currentTime) {  //
                const videoElement = element.querySelector("video");     //
                if (currentTime !== videoElement.currentTime) {          //
                    videoElement.currentTime = currentTime;              //
                }                                                        //
            });                                                          //
        }
    }
});

结果 (Result)

When finished, you should see something like the following JS Bin:

完成后,您应该会看到类似以下JS Bin的内容:

翻译自: https://davidwalsh.name/custom-html5-video

自定义视频播放控件样式

 类似资料: