{"id":28447,"date":"2021-05-18T22:03:02","date_gmt":"2021-05-18T22:03:02","guid":{"rendered":"http:\/\/Creating-a-Custom-Video-Player-in-Vue"},"modified":"2025-11-01T14:01:57","modified_gmt":"2025-11-01T21:01:57","slug":"creating-a-custom-video-player-in-vue","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/","title":{"rendered":"Creating a Custom Video Player in Vue.js"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>In this Media Jam you will learn how to create a video player with custom controls and event handling in Vue.js. This Jam does not use any pre-build video libraries, you will learn to code directly against the native <a href=\"https:\/\/cloudinary.com\/video_api\">video API<\/a> in the browser.<\/p>\n<p>Vue.js has great features to make components communicate together. In this Jam, you will learn how to use scoped slots, and how to pass information between different components.<\/p>\n<p>The video player is split up into multiple files:<\/p>\n<ol>\n<li>\n<p><code>Videoplayer.vue<\/code> contains the basics and wraps the native HTML5 video player in Vue code. It exposes video events, and its controls, so they are accessible for the other Vue components in the Jam.<\/p>\n<\/li>\n<li>\n<p><code>videoplayer-track.vue<\/code> listens to the <code>timeupdate<\/code> event, and based on the video duration, calculates the percentage played.<\/p>\n<\/li>\n<li>\n<p>And finally <code>app.vue<\/code>. This is the file where everything comes together. From custom controls to listening to native events, to showing the state of the video in the custom controls.<\/p>\n<\/li>\n<\/ol>\n<p>To see everything in action, feel free to check the CodeSandBox embedded below.<\/p>\n<\/div>\n\n\n  <div class=\"wp-block-cloudinary-code-sandbox \">\n    <iframe\n      src=\"https:\/\/codesandbox.io\/embed\/eloquent-mahavira-9kgrc?theme=dark&amp;codemirror=1&amp;highlights=&amp;editorsize=50&amp;fontsize=14&amp;expanddevtools=0&amp;hidedevtools=0&amp;eslint=0&amp;forcerefresh=0&amp;hidenavigation=0&amp;initialpath=%2F&amp;module=&amp;moduleview=0&amp;previewwindow=&amp;view=&amp;runonclick=1\"\n      height=\"500\"\n      style=\"width: 100%;\"\n      title=\"Custom Video Player in Vue.js\"\n      loading=\"lazy\"\n      allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n      sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n    ><\/iframe>\n  <\/div>\n\n\n<div class=\"wp-block-cloudinary-markdown \"><h2>Wrapping native HTML5 video in Vue.js<\/h2>\n<p>The native HTML5 video tag is constructed in \u2018videoplayer.vue\u2019. It is able to receive props (the ones we add in <code>app.vue<\/code>), and is able to pass these along to the video tag.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">template<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span>\n  <span class=\"hljs-attr\">:src<\/span>=<span class=\"hljs-string\">\"src\"<\/span>\n  <span class=\"hljs-attr\">:muted<\/span>=<span class=\"hljs-string\">\"muted\"<\/span>\n  <span class=\"hljs-attr\">:autoplay<\/span>=<span class=\"hljs-string\">\"autoplay\"<\/span>\n  <span class=\"hljs-attr\">:controls<\/span>=<span class=\"hljs-string\">\"controls\"<\/span>\n  <span class=\"hljs-attr\">:loop<\/span>=<span class=\"hljs-string\">\"loop\"<\/span>\n  <span class=\"hljs-attr\">:width<\/span>=<span class=\"hljs-string\">\"width\"<\/span>\n  <span class=\"hljs-attr\">:height<\/span>=<span class=\"hljs-string\">\"height\"<\/span>\n  <span class=\"hljs-attr\">:poster<\/span>=<span class=\"hljs-string\">\"poster\"<\/span>\n  <span class=\"hljs-attr\">:preload<\/span>=<span class=\"hljs-string\">\"preload\"<\/span>\n  <span class=\"hljs-attr\">:playsinline<\/span>=<span class=\"hljs-string\">\"true\"<\/span>\n  <span class=\"hljs-attr\">ref<\/span>=<span class=\"hljs-string\">\"player\"<\/span>\n\/&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">template<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span>&gt;<\/span><span class=\"javascript\">\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> {\n  <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">\"Videoplayer\"<\/span>,\n  <span class=\"hljs-attr\">props<\/span>: {\n    <span class=\"hljs-attr\">src<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">String<\/span>, <span class=\"hljs-attr\">required<\/span>: <span class=\"hljs-literal\">true<\/span> },\n    <span class=\"hljs-attr\">controls<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">Boolean<\/span>, <span class=\"hljs-attr\">required<\/span>: <span class=\"hljs-literal\">false<\/span>, <span class=\"hljs-attr\">default<\/span>: <span class=\"hljs-literal\">false<\/span> },\n    <span class=\"hljs-attr\">loop<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">Boolean<\/span>, <span class=\"hljs-attr\">required<\/span>: <span class=\"hljs-literal\">false<\/span>, <span class=\"hljs-attr\">default<\/span>: <span class=\"hljs-literal\">false<\/span> },\n    <span class=\"hljs-attr\">width<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">Number<\/span>, <span class=\"hljs-attr\">required<\/span>: <span class=\"hljs-literal\">false<\/span>, <span class=\"hljs-attr\">default<\/span>: <span class=\"hljs-number\">500<\/span> },\n    <span class=\"hljs-attr\">height<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">Number<\/span>, <span class=\"hljs-attr\">required<\/span>: <span class=\"hljs-literal\">false<\/span>, <span class=\"hljs-attr\">default<\/span>: <span class=\"hljs-number\">281<\/span> },\n    <span class=\"hljs-attr\">autoplay<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">Boolean<\/span>, <span class=\"hljs-attr\">required<\/span>: <span class=\"hljs-literal\">false<\/span>, <span class=\"hljs-attr\">default<\/span>: <span class=\"hljs-literal\">false<\/span> },\n    <span class=\"hljs-attr\">muted<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">Boolean<\/span>, <span class=\"hljs-attr\">required<\/span>: <span class=\"hljs-literal\">false<\/span>, <span class=\"hljs-attr\">default<\/span>: <span class=\"hljs-literal\">false<\/span> },\n    <span class=\"hljs-attr\">poster<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">String<\/span>, <span class=\"hljs-attr\">required<\/span>: <span class=\"hljs-literal\">false<\/span> },\n    <span class=\"hljs-attr\">preload<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">String<\/span>, <span class=\"hljs-attr\">required<\/span>: <span class=\"hljs-literal\">false<\/span>, <span class=\"hljs-attr\">default<\/span>: <span class=\"hljs-string\">\"auto\"<\/span> },\n  },\n<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>As you can see above, the default properties that the native <a href=\"https:\/\/cloudinary.com\/guides\/front-end-development\/html5-video-tag\">HTML5 video tag<\/a> has been passed through with Vue.js.<\/p>\n<p>Notice the <code>ref=&quot;player&quot;<\/code> prop on the video tag. This allows you to reference the native HTML tag in Vue.js like so: <code>this.$refs.player<\/code>.<\/p>\n<p>The player <code>ref<\/code> is used to send actions and to listen to events for the HTML5 video tag in Vue.<\/p>\n<h2>Methods to send actions to the player<\/h2>\n<p>The <code>videoplayer.vue<\/code> component has a bunch of functions to control the state of the native HTML5 player. Let\u2019s start with the basics: <code>play()<\/code>, <code>pause()<\/code>, <code>togglePlay()<\/code>, <code>mute()<\/code>, <code>unmute()<\/code> and <code>toggleMute()<\/code>. <em>Note that some of the earlier code around the props has been omitted to keep the below code simple and focused on the basic functions.<\/em><\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> {\n  <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">\"Videoplayer\"<\/span>,\n\t<span class=\"hljs-comment\">\/\/ keeping the state <\/span>\n\tdata() {\n    <span class=\"hljs-keyword\">return<\/span> {\n      <span class=\"hljs-attr\">playing<\/span>: <span class=\"hljs-literal\">false<\/span>,\n      <span class=\"hljs-attr\">videoMuted<\/span>: <span class=\"hljs-literal\">false<\/span>,\n    };\n  },\n <span class=\"hljs-attr\">methods<\/span>: {\n    play() {\n      <span class=\"hljs-keyword\">this<\/span>.$refs.player.play();\n      <span class=\"hljs-keyword\">this<\/span>.setPlaying(<span class=\"hljs-literal\">true<\/span>);\n    },\n\n    pause() {\n      <span class=\"hljs-keyword\">this<\/span>.$refs.player.pause();\n      <span class=\"hljs-keyword\">this<\/span>.setPlaying(<span class=\"hljs-literal\">false<\/span>);\n    },\n\n    togglePlay() {\n      <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">this<\/span>.playing) {\n        <span class=\"hljs-keyword\">this<\/span>.pause();\n      } <span class=\"hljs-keyword\">else<\/span> {\n        <span class=\"hljs-keyword\">this<\/span>.play();\n      }\n    },\n\n    setPlaying(state) {\n      <span class=\"hljs-keyword\">this<\/span>.playing = state;\n    },\n\t\t\n\t\tmute() {\n      <span class=\"hljs-keyword\">this<\/span>.$refs.player.muted = <span class=\"hljs-literal\">true<\/span>;\n      <span class=\"hljs-keyword\">this<\/span>.setMuted(<span class=\"hljs-literal\">true<\/span>);\n    },\n\n    unmute() {\n      <span class=\"hljs-keyword\">this<\/span>.$refs.player.muted = <span class=\"hljs-literal\">false<\/span>;\n      <span class=\"hljs-keyword\">this<\/span>.setMuted(<span class=\"hljs-literal\">false<\/span>);\n    },\n\n    toggleMute() {\n      <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">this<\/span>.videoMuted) {\n        <span class=\"hljs-keyword\">this<\/span>.unmute();\n      } <span class=\"hljs-keyword\">else<\/span> {\n        <span class=\"hljs-keyword\">this<\/span>.mute();\n      }\n    },\n\n    setMuted(state) {\n      <span class=\"hljs-keyword\">this<\/span>.videoMuted = state;\n    }\n\t}\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Some of this code looks a little redundant, especially the <code>setPlaying<\/code> and <code>setMuted<\/code> functions. But, these \u201csetter\u201d functions will be needed when you want to set the state of the player from another component while listening to video events.<\/p>\n<h2>Controlling the player from another component<\/h2>\n<p>By creating a base video player which sends events, and does simple actions, you can potentially create many video player instances with different feature sets without bloating the player code itself. In this Jam, the code is using a \u201cscoped slot\u201d to pass information and actions from the native video player to the code added into the slot. The controls, video track, and duration are created separately and put into the slot where the player is instantiated.<\/p>\n<p>This way you can make one player instance with just a play button, and another with a toggle play, a progress track, and a mute button.<\/p>\n<p>This is how you pass functions and other info into the slot:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">template<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span>\n      <span class=\"hljs-attr\">:src<\/span>=<span class=\"hljs-string\">\"src\"<\/span>\n      <span class=\"hljs-attr\">:muted<\/span>=<span class=\"hljs-string\">\"muted\"<\/span>\n      <span class=\"hljs-attr\">:autoplay<\/span>=<span class=\"hljs-string\">\"autoplay\"<\/span>\n      <span class=\"hljs-attr\">:controls<\/span>=<span class=\"hljs-string\">\"controls\"<\/span>\n      <span class=\"hljs-attr\">:loop<\/span>=<span class=\"hljs-string\">\"loop\"<\/span>\n      <span class=\"hljs-attr\">:width<\/span>=<span class=\"hljs-string\">\"width\"<\/span>\n      <span class=\"hljs-attr\">:height<\/span>=<span class=\"hljs-string\">\"height\"<\/span>\n      <span class=\"hljs-attr\">:poster<\/span>=<span class=\"hljs-string\">\"poster\"<\/span>\n      <span class=\"hljs-attr\">:preload<\/span>=<span class=\"hljs-string\">\"preload\"<\/span>\n      <span class=\"hljs-attr\">:playsinline<\/span>=<span class=\"hljs-string\">\"true\"<\/span>\n      <span class=\"hljs-attr\">ref<\/span>=<span class=\"hljs-string\">\"player\"<\/span>\n    \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">slot<\/span>\n      <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"controls\"<\/span>\n      *<span class=\"hljs-attr\">:play<\/span>=<span class=\"hljs-string\">\"play\"<\/span>\n      <span class=\"hljs-attr\">:pause<\/span>=<span class=\"hljs-string\">\"pause\"<\/span>\n      <span class=\"hljs-attr\">:playing<\/span>=<span class=\"hljs-string\">\"playing\"<\/span>\n      <span class=\"hljs-attr\">:toggle-play<\/span>=<span class=\"hljs-string\">\"togglePlay\"<\/span>\n      <span class=\"hljs-attr\">:video-muted<\/span>=<span class=\"hljs-string\">\"videoMuted\"<\/span>\n      <span class=\"hljs-attr\">:toggle-mute<\/span>=<span class=\"hljs-string\">\"toggleMute\"<\/span>*\n    &gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">slot<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">template<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>In the example above the functions and state created earlier are passed into the slot called \u201ccontrols\u201d. When the slot is used by another component, that component receives all these properties and functions.<\/p>\n<p>Use it like this:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">template<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">videoplayer<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/res.cloudinary.com\/demo\/video\/upload\/dog.mp4\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">template<\/span> <span class=\"hljs-attr\">v-slot:controls<\/span>=<span class=\"hljs-string\">\"{ togglePlay, toggleMute, playing, videoMuted }\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"videoplayer-controls\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> @<span class=\"hljs-attr\">click<\/span>=<span class=\"hljs-string\">\"togglePlay()\"<\/span>&gt;<\/span>{{ playing ? \"pause\" : \"play\" }}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> @<span class=\"hljs-attr\">click<\/span>=<span class=\"hljs-string\">\"toggleMute()\"<\/span>&gt;<\/span>{{ videoMuted ? \"unmute\" : \"mute\" }}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n\t    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\t\t<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">template<\/span>&gt;<\/span>\n\t<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">videoplayer<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">template<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span>&gt;<\/span><span class=\"javascript\">\n<span class=\"hljs-keyword\">import<\/span> videoplayer <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/components\/videoplayer\"<\/span>;\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> {\n  <span class=\"hljs-attr\">components<\/span>: {\n    videoplayer,\n  },\n}\n<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h2>Sending events from the native video to the player implementation<\/h2>\n<p>Now that the \u201cscoped slot\u201d is working, you can use the Vue.js event emitter to send native video events to the <code>app.vue<\/code> which implements <code>videoplayer.vue<\/code>.<\/p>\n<p>These are the most interesting events in most cases:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> EVENTS = &#91;\n  <span class=\"hljs-string\">\"play\"<\/span>,\n  <span class=\"hljs-string\">\"pause\"<\/span>,\n  <span class=\"hljs-string\">\"ended\"<\/span>,\n  <span class=\"hljs-string\">\"loadeddata\"<\/span>,\n  <span class=\"hljs-string\">\"waiting\"<\/span>,\n  <span class=\"hljs-string\">\"playing\"<\/span>,\n  <span class=\"hljs-string\">\"timeupdate\"<\/span>,\n  <span class=\"hljs-string\">\"canplay\"<\/span>,\n  <span class=\"hljs-string\">\"canplaythrough\"<\/span>,\n  <span class=\"hljs-string\">\"statechanged\"<\/span>,\n];\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>In the <code>mounted<\/code> hook of the Vue component, when the video tag exists in the DOM, you can loop over these events, and start listening to them. <em>Some code is omitted to make the example more clear. For the full code see the CodeSandBox link.<\/em><\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> EVENTS = &#91;\n  <span class=\"hljs-string\">\"play\"<\/span>,\n  <span class=\"hljs-string\">\"pause\"<\/span>,\n  <span class=\"hljs-string\">\"ended\"<\/span>,\n  <span class=\"hljs-string\">\"loadeddata\"<\/span>,\n  <span class=\"hljs-string\">\"waiting\"<\/span>,\n  <span class=\"hljs-string\">\"playing\"<\/span>,\n  <span class=\"hljs-string\">\"timeupdate\"<\/span>,\n  <span class=\"hljs-string\">\"canplay\"<\/span>,\n  <span class=\"hljs-string\">\"canplaythrough\"<\/span>,\n  <span class=\"hljs-string\">\"statechanged\"<\/span>,\n];\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> {\n  <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">\"Videoplayer\"<\/span>,\n\tmounted() {\n\t  <span class=\"hljs-keyword\">this<\/span>.bindEvents();\n\t},\n\t<span class=\"hljs-attr\">methods<\/span>: {\n\t  bindEvents() {\n\t    EVENTS.forEach(<span class=\"hljs-function\">(<span class=\"hljs-params\">event<\/span>) =&gt;<\/span> {\n\t      <span class=\"hljs-keyword\">this<\/span>.bindVideoEvent(event);\n\t    });\n\t  },\n\t\n\t  bindVideoEvent(which) {\n\t    <span class=\"hljs-keyword\">const<\/span> player = <span class=\"hljs-keyword\">this<\/span>.$refs.player;\n\t\n\t    player.addEventListener(\n\t      which,\n\t      (event) =&gt; {\n\t        <span class=\"hljs-keyword\">this<\/span>.$emit(which, { event, <span class=\"hljs-attr\">player<\/span>: <span class=\"hljs-keyword\">this<\/span> });\n\t      }\n\n\t    );\n\t  },\n\t}\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>On <code>mounted<\/code> the <code>bindEvents()<\/code> function loops over the list of pre-defined events and it fires off a <code>bindVideoEvent()<\/code> function which, in turn, takes the video DOM node from <code>this.$refs.player<\/code>, and adds an <code>addEventListener<\/code> function for the event.<\/p>\n<p>Now that the code is listening to the events from the native player, it uses the Vue event emitter to send events. It sends the native event data to itself <em>and<\/em> the player instance. This is handy for the component implementing the video player.<\/p>\n<p>This is how to listen to the events from the other side:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">template<\/span>&gt;<\/span>\n\t<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">videoplayer<\/span>\n\t\t<span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/res.cloudinary.com\/demo\/video\/upload\/dog.mp4\"<\/span>\n\t\t@<span class=\"hljs-attr\">play<\/span>=<span class=\"hljs-string\">\"onPlayerPlay\"<\/span>\n\t\t@<span class=\"hljs-attr\">pause<\/span>=<span class=\"hljs-string\">\"onPlayerPause\"<\/span>\n\t\t@<span class=\"hljs-attr\">ended<\/span>=<span class=\"hljs-string\">\"onPlayerEnded\"<\/span>\n\t\t@<span class=\"hljs-attr\">loadeddata<\/span>=<span class=\"hljs-string\">\"onPlayerLoadeddata\"<\/span>\n\t\t@<span class=\"hljs-attr\">waiting<\/span>=<span class=\"hljs-string\">\"onPlayerWaiting\"<\/span>\n\t\t@<span class=\"hljs-attr\">playing<\/span>=<span class=\"hljs-string\">\"onPlayerPlaying\"<\/span>\n\t\t@<span class=\"hljs-attr\">timeupdate<\/span>=<span class=\"hljs-string\">\"onPlayerTimeupdate\"<\/span>\n\t\t@<span class=\"hljs-attr\">canplay<\/span>=<span class=\"hljs-string\">\"onPlayerCanplay\"<\/span>\n\t\t@<span class=\"hljs-attr\">canplaythrough<\/span>=<span class=\"hljs-string\">\"onPlayerCanplaythrough\"<\/span>\n\t\t@<span class=\"hljs-attr\">statechanged<\/span>=<span class=\"hljs-string\">\"playerStateChanged\"<\/span>&gt;<\/span>\n\n\t<span class=\"hljs-comment\">&lt;!-- slot related stuff --&gt;<\/span>\n\n\t<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">videoplayer<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">template<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span>&gt;<\/span><span class=\"javascript\">\n<span class=\"hljs-keyword\">import<\/span> videoplayer <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/components\/videoplayer\"<\/span>;\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> {\n  <span class=\"hljs-attr\">components<\/span>: {\n    videoplayer,\n  },\n  <span class=\"hljs-attr\">methods<\/span>: {\n    onPlayerPlay({ event, player }) {\n      <span class=\"hljs-built_in\">console<\/span>.log(event.type);\n      player.setPlaying(<span class=\"hljs-literal\">true<\/span>);\n    },\n    onPlayerPause({ event, player }) {\n      <span class=\"hljs-built_in\">console<\/span>.log(event.type);\n      player.setPlaying(<span class=\"hljs-literal\">false<\/span>);\n    },\n    onPlayerEnded({ event, player }) {\n      <span class=\"hljs-built_in\">console<\/span>.log(event.type);\n      player.setPlaying(<span class=\"hljs-literal\">false<\/span>);\n    },\n    onPlayerLoadeddata({ event }) {\n      <span class=\"hljs-built_in\">console<\/span>.log(event.type);\n    },\n    onPlayerWaiting({ event }) {\n      <span class=\"hljs-built_in\">console<\/span>.log(event.type);\n    },\n    onPlayerPlaying({ event }) {\n      <span class=\"hljs-built_in\">console<\/span>.log(event.type);\n    },\n    onPlayerTimeupdate({ event }) {\n      <span class=\"hljs-built_in\">console<\/span>.log({ <span class=\"hljs-attr\">event<\/span>: event.type, <span class=\"hljs-attr\">time<\/span>: event.target.currentTime });\n    },\n    onPlayerCanplay({ event }) {\n      <span class=\"hljs-built_in\">console<\/span>.log(event.type);\n    },\n    onPlayerCanplaythrough({ event }) {\n      <span class=\"hljs-built_in\">console<\/span>.log(event.type);\n    },\n\n    playerStateChanged({ event }) {\n      <span class=\"hljs-built_in\">console<\/span>.log(event.type);\n    },\n  },\n};\n<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>By combining the \u201cscoped slot\u201d and the event listeners, you can do anything you need in order to ensure that the player looks and behaves the way you want each time you implement it.<\/p>\n<h2>Let\u2019s add a time indicator<\/h2>\n<p>Now that all tools are in place, let\u2019s add a time indicator. For the time indicator, you need the current time of the video and the duration of the video. On top of that, you also need a function to convert seconds to a duration.<\/p>\n<p>In <code>app.vue<\/code>, you ask for the video <code>duration<\/code> and the <code>convertTimeToDuration<\/code> function. You also have to listen to the <code>timeupdate<\/code> event of the <a href=\"https:\/\/cloudinary.com\/glossary\/native-video\">native video<\/a> to get the <code>currentTime<\/code> of the video.<\/p>\n<p><em>Note that all other code is removed so the example stays simple.<\/em><\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">template<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">videoplayer<\/span> \n\t\t<span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/res.cloudinary.com\/demo\/video\/upload\/dog.mp4\"<\/span>\n\t\t@<span class=\"hljs-attr\">timeupdate<\/span>=<span class=\"hljs-string\">\"onPlayerTimeupdate\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">template<\/span> <span class=\"hljs-attr\">v-slot:controls<\/span>=<span class=\"hljs-string\">\"{ duration, convertTimeToDuration }\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"videoplayer-controls\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"videoplayer-controls-time\"<\/span>&gt;<\/span>\n          {{ convertTimeToDuration(time) }} \/\n          {{ convertTimeToDuration(duration) }}\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\t    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\t\t<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">template<\/span>&gt;<\/span>\n\t<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">videoplayer<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">template<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span>&gt;<\/span><span class=\"javascript\">\n<span class=\"hljs-keyword\">import<\/span> videoplayer <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/components\/videoplayer\"<\/span>;\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> {\n  <span class=\"hljs-attr\">components<\/span>: {\n    videoplayer,\n  },\n\tdata() {\n    <span class=\"hljs-keyword\">return<\/span> {\n      <span class=\"hljs-attr\">time<\/span>: <span class=\"hljs-number\">0<\/span>,\n    };\n  },\n  <span class=\"hljs-attr\">methods<\/span>: {\n\t\tonPlayerTimeupdate({ event }) {\n      <span class=\"hljs-keyword\">this<\/span>.time = event.target.currentTime;\n    },\n\t}\n}\n<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>In <code>videoplayer.vue<\/code>, you need to manage to get the <code>duration<\/code> and create the code for the <code>convertTimeToDuration<\/code> function.<\/p>\n<p>For the <code>duration<\/code>, update the <code>bindVideoEvent<\/code> function from earlier. When the <code>loadeddata<\/code> event hits the video, \u2018duration\u2019 becomes available.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">data() {\n  <span class=\"hljs-keyword\">return<\/span> {\n    <span class=\"hljs-attr\">duration<\/span>: <span class=\"hljs-number\">0<\/span>,\n  };\n},\n<span class=\"hljs-attr\">methods<\/span>: {\n\tbindVideoEvent(which) {\n\t  <span class=\"hljs-keyword\">const<\/span> player = <span class=\"hljs-keyword\">this<\/span>.$refs.player;\n\t\n\t  player.addEventListener(\n\t    which,\n\t    (event) =&gt; {\n\t      <span class=\"hljs-keyword\">if<\/span> (which === <span class=\"hljs-string\">\"loadeddata\"<\/span>) {\n\t        <span class=\"hljs-keyword\">this<\/span>.duration = player.duration;\n\t      }\n\t\n\t      <span class=\"hljs-keyword\">this<\/span>.$emit(which, { event, <span class=\"hljs-attr\">player<\/span>: <span class=\"hljs-keyword\">this<\/span> });\n\t    },\n\t    <span class=\"hljs-literal\">true<\/span>\n\t  );\n\t},\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This is a simple function, used to parse seconds, and turn them into a duration. Add this one to your methods object in <code>videoplayer.vue<\/code>:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">convertTimeToDuration(seconds) {\n  <span class=\"hljs-keyword\">return<\/span> &#91;<span class=\"hljs-built_in\">parseInt<\/span>((seconds \/ <span class=\"hljs-number\">60<\/span>) % <span class=\"hljs-number\">60<\/span>, <span class=\"hljs-number\">10<\/span>), <span class=\"hljs-built_in\">parseInt<\/span>(seconds % <span class=\"hljs-number\">60<\/span>, <span class=\"hljs-number\">10<\/span>)]\n    .join(<span class=\"hljs-string\">\":\"<\/span>)\n    .replace(<span class=\"hljs-regexp\">\/\\b(\\d)\\b\/g<\/span>, <span class=\"hljs-string\">\"0$1\"<\/span>);\n},\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h2>Conclusion<\/h2>\n<p>Now that all conditions of working in the architecture are in place, it becomes clear it is very flexible, and adding the video player track component should be easy. Check the CodeSandBox link for the full example.<\/p>\n<p>Happy coding!<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":28448,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[134,371,303,315],"class_list":["post-28447","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-guest-post","tag-under-review","tag-video","tag-vue"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Creating a Custom Video Player in Vue.js<\/title>\n<meta name=\"description\" content=\"In this Media Jam you will learn how to create a video player with custom controls and event handling in Vue.js.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Creating a Custom Video Player in Vue.js\" \/>\n<meta property=\"og:description\" content=\"In this Media Jam you will learn how to create a video player with custom controls and event handling in Vue.js.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2021-05-18T22:03:02+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-11-01T21:01:57+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924512\/Web_Assets\/blog\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041.jpg?_i=AA\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Creating a Custom Video Player in Vue.js\",\"datePublished\":\"2021-05-18T22:03:02+00:00\",\"dateModified\":\"2025-11-01T21:01:57+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/\"},\"wordCount\":8,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924512\/Web_Assets\/blog\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041.jpg?_i=AA\",\"keywords\":[\"Guest Post\",\"Under Review\",\"Video\",\"Vue\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2021\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/\",\"name\":\"Creating a Custom Video Player in Vue.js\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924512\/Web_Assets\/blog\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041.jpg?_i=AA\",\"datePublished\":\"2021-05-18T22:03:02+00:00\",\"dateModified\":\"2025-11-01T21:01:57+00:00\",\"description\":\"In this Media Jam you will learn how to create a video player with custom controls and event handling in Vue.js.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924512\/Web_Assets\/blog\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924512\/Web_Assets\/blog\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041.jpg?_i=AA\",\"width\":6016,\"height\":4016},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Creating a Custom Video Player in Vue.js\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Creating a Custom Video Player in Vue.js","description":"In this Media Jam you will learn how to create a video player with custom controls and event handling in Vue.js.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/","og_locale":"en_US","og_type":"article","og_title":"Creating a Custom Video Player in Vue.js","og_description":"In this Media Jam you will learn how to create a video player with custom controls and event handling in Vue.js.","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/","og_site_name":"Cloudinary Blog","article_published_time":"2021-05-18T22:03:02+00:00","article_modified_time":"2025-11-01T21:01:57+00:00","twitter_card":"summary_large_image","twitter_image":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924512\/Web_Assets\/blog\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041.jpg?_i=AA","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/"},"author":{"name":"","@id":""},"headline":"Creating a Custom Video Player in Vue.js","datePublished":"2021-05-18T22:03:02+00:00","dateModified":"2025-11-01T21:01:57+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/"},"wordCount":8,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924512\/Web_Assets\/blog\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041.jpg?_i=AA","keywords":["Guest Post","Under Review","Video","Vue"],"inLanguage":"en-US","copyrightYear":"2021","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/","name":"Creating a Custom Video Player in Vue.js","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924512\/Web_Assets\/blog\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041.jpg?_i=AA","datePublished":"2021-05-18T22:03:02+00:00","dateModified":"2025-11-01T21:01:57+00:00","description":"In this Media Jam you will learn how to create a video player with custom controls and event handling in Vue.js.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924512\/Web_Assets\/blog\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924512\/Web_Assets\/blog\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041.jpg?_i=AA","width":6016,"height":4016},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/creating-a-custom-video-player-in-vue\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Creating a Custom Video Player in Vue.js"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681924512\/Web_Assets\/blog\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041\/e8f187e504dba40f11ee32d9db88d6182e8186be-6016x4016-1_284485b041.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28447","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=28447"}],"version-history":[{"count":4,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28447\/revisions"}],"predecessor-version":[{"id":39067,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28447\/revisions\/39067"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/28448"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28447"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28447"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28447"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}