Videos

Fetching a list of videos

You can fetch a list of videos using the following method on the client. fetchVideos returns a Query object filled with VideoInstance objects of the request or undefined in case of failure. There are a few examples provided using Vue and tailwindcss.

const videos = await client.fetchVideos(filter, order, withProducts, count, after);

// Log the title of each video.
videos.items.forEach(video => {
    console.log(video.title);
})

Parameters

filter: VideoFilter

Filter videos based on set criteria. Defaults to {}.

type VideoFilter = {
  author?: IdType;
  search?: string;
  type?: string;
  category?: IdType;
  seller?: IdType;
  status?: VideoStatus[];   // "INITIAL" | "PLANNED" | "LIVE" | "PAST" | "NOT_RECORDED"
};

(optional) order: OrderType<VideoOrderField>

Order the result. Defaults to { field: "ADDED", direction: "DESC" }.

const order = {
    field: "ADDED";     // "TITLE" | "ADDED" | "VIEWS" | "LIKES" | "COMMENTS" | "RECOMMENDATION"
    direction: 'ASC';   // "ASC" | "DESC"
};

(optional) withProducts: boolean

Include products in the query result. Defaults to false.

(optional) count: number

Number of videos to fetch. Defaults to 20.

(optional) after: string

Cursor at which to start querying. Can be obtained from _cursor property of each video. Defaults to null.

Example

Fetching a list of videos with an option to load more and a search functionality.

<script setup>
import { onMounted, ref, watch } from "vue";
import { Client, FetchClientAdapter } from "livemarket-sdk";

let client = null;
const videos = ref(null);
const search = ref('');

const fetchVideos = async () => {
  videos.value = await client.fetchVideos(
    {
      search: search.value,
    },
    null,
    false,
    5
  );
};

const fetchMore = async () => {
  videos.value = await videos.value.fetchMore();
};

onMounted(() => {
  // in a real-world scenario you would move this step to a separate file.
  client = new Client({
    token: "###YOUR_CLIENT_TOKEN##",
    adapter: new FetchClientAdapter(),
  });

  fetchVideos();
});

watch(search, fetchVideos);
</script>

<template>
  <div class="py-6">
    <input
      class="w-full px-4 py-2 rounded-lg mb-4"
      placeholder="Type something in to search..."
      v-model="search"
    />
    <ul
      class="list-none bg-pink-700 bg-opacity-30 p-3 rounded-lg"
      v-if="videos"
    >
      <li class="text-lg text-bold" v-for="video in videos.items">
        <div>{{ video.title }}</div>
        <div class="text-sm">[ID: {{ video.id }}]</div>
      </li>
    </ul>
    <div
      class="flex justify-center items-center py-2"
      v-if="!videos || videos.isFetching"
    >
      <div
        class="w-8 h-8 border-2 border-solid border-white border-t-transparent rounded-full animate-spin"
      />
    </div>
    <div
      class="flex justify-center items-center mt-4"
      v-if="videos && videos.totalCount > videos.items.length"
    >
      <button
        class="cursor-pointer transition-colors hover:opacity-75"
        :disabled="videos.isFetching"
        @click="fetchMore"
      >
        Load more
      </button>
    </div>
  </div>
</template>

Fetching a single video

You can fetch a single video using fetchVideo. Returns VideoInstance on success and undefined on failure.

const video = await client.fetchVideo(globalId, options);

Parameters

globalId: IdType

globalId property of the requested video.

(optional) options: FetchVideoOptions

Options for the video.

{
  withProducts: true,
  withComments: true,
  liveChat: true,
  liveUpdates: false  // Live status updates for the video.
}

Note: liveChat and liveUpdates require a Pusher instance to work. See Client options

Example

LiveMarket videos are hosted on Mux. In order to play them you need to install the videojs library with Mux plugin. Please refer to the Mux documentation for more information.

<script setup>
import videojs from "@mux/videojs-kit";
import "@mux/videojs-kit/dist/index.css";
import { nextTick, onMounted, ref } from "vue";
import { Client, FetchClientAdapter } from "livemarket-sdk";
import Spinner from "...";

let client = null;
const video = ref(null);
const playerElement = ref(null);
const player = ref(null);

const fetchVideo = async () => {
  // Disable live chat as we aren't using it.
  video.value = await client.fetchVideo("...", {
    liveChat: false
  });

  nextTick(() => {
    player.value = videojs(playerElement.value, {
      timelineHoverPreviews: true,
      plugins: {
        mux: {
          debug: true,
          data: {
            env_key: import.meta.env.PUBLIC_MUX_KEY,
            video_title: video.value.title,
          },
        },
      },
    });

    player.value.src({
      src: video.value.muxId,
      type: "video/mux",
    });
  });
};

onMounted(() => {
  client = new Client({
    token: "###YOUR_CLIENT_TOKEN##",
    adapter: new FetchClientAdapter(),
  });

  fetchVideo();
});
</script>

<template>
  <div class="w-full py-6 text-center">
    <div v-if="video">
      <h4>{{ video.title }}</h4>
      <h5 class="!mt-2">{{ video.added.toLocaleDateString() }}</h5>
      <div class="px-4">
        <video
          ref="playerElement"
          class="video-js h-96 w-auto mx-auto"
          controls
          preload="auto"
          width="100%"
        ></video>
      </div>
      <h4>Featured in this video</h4>
      <div class="px-2 flex justify-start items-start w-full overflow-x-auto">
        <div
          v-for="videoProduct in video.videoProducts"
          :key="videoProduct.product.id"
          class="p-2 w-48 rounded-md transition-all hover:shadow-2xl"
        >
          <img
            class="block w-full h-auto"
            :src="videoProduct.product.thumbnail.url"
            :alt="videoProduct.product.thumbnail.alt"
          />

          <h5>{{ videoProduct.product.displayName }}</h5>
          <h6>{{ videoProduct.product.displayPrice }}</h6>
        </div>
      </div>
    </div>
    <div class="flex justify-center items-center" v-else>
      <Spinner />
    </div>
  </div>
</template>

Chat example

This is an example of using chat associated with a video. You can combine it with the example above to create a full video page.

<script setup>
import { onMounted, ref } from "vue";
import { Client, FetchClientAdapter } from "../../../lib/livemarket-sdk";
import Spinner from "...";
import Pusher from "pusher-js";

let client = null;
const video = ref(null);
const chat = ref([]);
const chatMessage = ref("");
const nickname = ref("Test user");

const fetchVideo = async () => {
  video.value = await client.fetchVideo("...", {
    withProducts: false,
  });

  chat.value = video.value.comments?.reverse() || [];

  video.value.addEventListener("new-chat-message", (e) => {
    // e.details contains the message received
    chat.value.unshift(e.details);
  });
};

const sendChatMessage = () => {
  if (!chatMessage.value || !nickname.value) {
    return;
  }

  video.value.sendChatMessage(chatMessage.value, nickname.value);
  chatMessage.value = "";
};

onMounted(() => {
  client = new Client({
    token: "###YOUR_CLIENT_TOKEN##",
    adapter: new FetchClientAdapter(),
    pusher: Pusher,
    pusherKey: "###YOUR_PUSHER_KEY###",
  });

  fetchVideo();
});
</script>

<template>
  <div class="w-full py-6 text-center">
    <div v-if="video">
      <h4>{{ video.title }}</h4>
      <h5 class="!mt-2">{{ video.added.toLocaleDateString() }}</h5>
      <form class="text-left" @submit.prevent="sendChatMessage">
        <h5>Chat</h5>
        <input
          class="block w-full px-4 py-2 rounded-lg mb-2"
          type="text"
          placeholder="Nickname"
          v-model="nickname"
        />
        <input
          class="block w-full px-4 py-2 rounded-lg mb-2"
          type="text"
          placeholder="Message"
          v-model="chatMessage"
        />
        <button class="cursor-pointer" types="submit">Send</button>
      </form>
      <div class="p-3 rounded-lg text-left max-h-48 overflow-y-auto">
        <div v-for="message in chat">
          <p>
            <strong>{{ message.user.firstName || "Unknown user" }}: </strong>
            {{ message.comment }}
          </p>
          <p>
            {{ message.submitDate.toLocaleDateString() }}
          </p>
        </div>
      </div>
    </div>
    <div class="flex justify-center items-center" v-else>
      <Spinner />
    </div>
  </div>
</template>

VideoInstance object

Represents a single video and it’s available actions.

{
    id: ...,
    globalId: "1234",
    title: "A Random Video",
    added: "2022-03-17T17:57:19.790706+00:00",
    resultFile: ...,
    isHorizontal: false,
    videoStartTime: null,
    status: "INITIAL",
    isLiked: true,
    author: ...,
    videoStats: {
        likes: 36,
        comments: 42,
        views: 9
    },
    videoImage: {
        url: ...,
        alt: null
    },
    comments: []
}

Properties

id: IdType

Id of the video.

globalId: IdType

Secondary id of the video. May be null.

title: string

Title of the video.

author: user

Author of the video.

See User for more information.

resultFile: string

URL of the video file. May be null.

isHorizontal: boolean

Is the video in horizontal orientation?

added: Date

Video publication date. May be null.

videoStartTime: Date

Time the video started or planned to start at. May be null.

videoImage: Image

Thumbnail of the video. May be null.

See Image from more details.

videoStats: VideoStats

Number of likes, comments and views of the video. May be null.

type VideoStats = {
  likes: number;
  comments: number;
  views: number;
};

status: VideoStatus

Current status of the video. May be null.

Available statuses are: INITIAL | PLANNED | LIVE | PAST | NOT_RECORDED

videoProducts: VideoProduct[]

List of products in the video. May be null.

type VideoProduct = {
  product: Product;
};

See Product for more information.

comments: Comment[]

List of comments under the video.

See CommentInstance.

isLiked: boolean

Is the video liked by the current user.

(readonly) muxId: string

Mux ID of the video.

Methods

addVideoView: Promise<number | undefined>

Add a view to the video. Returns the current number of views on success or undefined on failure.

const views = await video.addVideoView();

Note: You should only manually add a view to the video if it takes a center stage in your layout and was fetched using fetchVideos (fetch a collection of videos). fetchVideo automatically adds a view to the video.

enableLiveChat: void

Subscribes to video’s live chat. The video will receive updates to it’s comments property in real time. The VideoInstance object will also start to dispatch new-chat-message event for every message sent to the chat.

...
video.enableLiveChat();
...

Note: If you’ve set liveChat parameter to true when calling fetchVideo, this is already enabled and you don’t need to call this function.

enableLiveUpdates: void

Enables live updates to video status through Pusher.

...
video.enableLiveUpdates();
...

dislikeVideo: Promise<boolean>

Dislike the video. Returns current isLiked value.

const isLiked = await video.dislikeVideo();

likeVideo: Promise<boolean>

Like the video. Returns current isLiked value.

const isLiked = await video.likeVideo();

sendChatMessage: Promise<CommentInstance | undefined>

Send a message in the video’s chat. Returns CommentInstance on success or undefined on failure.

const message = await video.sendChatMessage(message, nick);
Parameters
message: string

A message to send.

(optional) nick: string

Nickname to appear next to the message. Defaults to the nickname of current TemporaryUser or overrides this nickname if set.

You can set user’s nickname beforehand. See Set user’s nickname

Events

new-chat-message

Emitted when a new message has been posted to video’s chat.

video.addEventListener('new-chat-message', (event: LMEvent<CommentInstance>) => {
  const comment = event.details;

  console.log(comment);
  /**
   * {
   *    id: ...,
   *    user: ...,
   *    comment: ...,
   *    submitDate: ...,
   *    isRemoved: ...
   * }
   */
});

CommentInstance object

Properties

id: IdType

Id of the comment.

user: User

Author of the comment. May be null.

comment: string

Content of the comment.

submitDate: Date

Date the comment was posted.

isRemoved: boolean

Was the comment removed?

Methods

isMine: boolean

Returns true if this comment belongs to the current user. Otherwise false.

comments.forEach(comment => {
  if(comment.isMine()) {
    console.log(`=== ${comment.comment} ===`);
  }
  else {
    console.log(comment.comment);
  }
})