Skip to main content

Implementing Pipes2 feeds Guide

This guide will walk you through implementing your own custom Pipes2 feeds to be able to serve your content in Zapp mobile and TV apps.

In this guide we'll use the express framework to showcase our examples, but note that you can use any framework / platform that can serve JSON responses.

info

Implementations notes

  • The feeds will be requested by the end user devices - make sure the feeds are hosted and cached properly.
  • Your server should enable cors. Without enabling cors, you won't be able to serve the feeds on Samsung & LG and you might have issues debugging your feeds on our web based debugger tool.

For more info about cors see here

In this guide we will cover the following:

  • Creating a simple Pipes2 feed (e.g used for featured items on the home screen, featuring the all shows list, featuring all live channels etc.)
  • Creating a dynamic Pipes2 feed (e.g used for building feeds for inner screens like showing all episodes or seasons of a series given a series id, showing a list of all movies in a specific genre given a genre id).
  • Creating a search feed.
  • Protecting content streams.

Creating a simple Pipes2 feed

Following is an example of a featured items feed. For brevity it presents two items (A teaser for a show and a teaser for a live channel).

info

For more information on the feed schema and available fields see The Pipes2 JSON schema API

note

Pay attention to the inline comments.


const express = require("express");


const app = express();
const port = 8080;

// ...
// This example hardcodes the feed info,
// but in your implementation you should render it
// according to your business logic.

const featuredFees = {
id: "/featured/items", // Any unique id can be set
title: "Featured Items",
entry: [
{
id: "show-1",
title: "Show 1 Title",
summary: "Show 1 Summary",
type: {
// Tapping on the item in the app will
// open the screen that is type mapped to the `show` type in Zapp studio.
// You can set any type as you want according to your needs
// as long as it is mapped on Zapp studio configuration with the same name.
// For more info see https://docs.applicaster.com/using-zapp/app-building-walk-through#add-type-mapping
value: "show",
},
media_group: [
{
type: "image",
media_item: [
// You can pass an array of images and choose which image
// to show by setting the image key on the cell style
// inside the Zapp studio.
{
src: "https://example.com/images/show-1-image.png",
key: "image_base",
},
],
},
],
},
{
id: "live-1",
title: "Live Channel 1",
summary: "Channel 1 Summary",
type: {
// Tapping on the item in the device will
// open the screen that is type mapped to `live` in Zapp studio.
value: "live",
},
content: {
type: "video/hls",
src: "https://example.com/big-brother-s1-e1.m3u8",
},
media_group: [
{
type: "image",
media_item: [
{
src: "https://example.com/images/live-1-image.png",
key: "image_base",
},
],
},
],

},
],
};

app.get("/featured/items", (req, res) => {
res.set("Content-Type", "application/vnd+applicaster.pipes2+json").send(featuredFees);
});

// ...
app.listen(port, () => {
console.log(`Example app listening at port ${port}`);
});


Creating a dynamic Pipes2 feed

A dynamic feed is a feed that is called from an inner screen of the app. For example presenting show seasons, season episodes or movies in a specific genre.

Following is an example of a feed that servers the show's episodes by showId.

info

For more information on the feed structure see The Pipes2 JSON schema API and Setting up dynamic feed params

note

Pay attention to the inline comments.

// getEpisodesByShowId holds your custom business logic
// and database integration.
import getEpisodesByShowId from "./models/episodes";

// As you can see below, the `showId` is passed as a path param.
// To refer to this URL inside Zapp, the Zapp user should create the following feed:
// `https://example.com/shows/{{showId}}`
// (replace example.com with your domain/api path).

// When the Zapp user sets up the feed in the component, Zapp Studio will detect
// that there is a dynamic param called `showId`.
// Param values can be retrieved from the referring entry.

// If, for example, the Zapp user wants to configure this feed in a component on the show
// screen, she/he should set the `showId` param to `entry.id`.
// Now when an app user will tap on a component with a show,
// it will open the show screen and populate the `showId` with the id of the
// show.
// Find out more here - https://docs.applicaster.com/using-zapp/content/using-feeds-inside-layouts

app.get("/shows/:showId", async (req, res) => {
const episodesByShowId = await getEpisodesByShowId(req.params.showId);

// This custom example function will translate the data coming form
// your API to a valid Pipes2 JSON.
// If the function returns errors, please make sure you pass
// them to the response with the relevant http status code.
function renderEpisodeFeeds(modelResponse) {
// Lets say the getEpisodesByShowId returns the following array
// of episodes:
// {
// title: "Show title"
// items: [
// {
// id: "episode-98723",
// title: "episode name",
// description: "episode description",
// image: "https://example.com/images/episode-98723.png",
// episodeNumber: 1,
// source: "https://example.com/episode-98723.h3u8"
// },
// ...
// ]
// }
return {
id: `/shows/${req.params.showId}`,
title: modelResponse.title,
entry: modelResponse.items((episode) => {
return {
id: episode.id,
title: episode.title,
summary: episode.description,
type: {
// Tapping on the item in the app will
// open the screen that is type mapped to `video` in Zapp studio.
// This screen should hold the app's player.
value: "video",
},
content: {
type: "video/hls",
src: episode.source,
},
media_group: [
{
type: "image",
media_item: [
{
src: episode.image,
key: "image_base",
},
],
},
],
};
}),
};
}

res
.set("Content-Type", "application/vnd+applicaster.pipes2+json")
.send(renderEpisodeFeeds(episodesByShowId));
});


Creating a search feed

A search feed is a different type of a dynamic feed. Instead of getting the feed from the referring entry (as seen in the example above), it gets the search string from the search input box on the app's search screen.

info

For more information on the feed structure see The Pipes2 JSON schema API

note

Pay attention to the inline comments.


// As you can see the `q`(query string) is passed as a query path param.
// NOTE that using `q` as a query string is mandatory (e.g cannot be renamed) for search.
// To refer to this URL inside Zapp, the Zapp user should create the following feed.
// `https://example.com/q?={{q}}
//(replace example.com with your domain/ api path)

app.get("/search?q={{q}}", async (req, res) => {
const episodes = await searchEpisodes(req.query.q);

// This function will translate the data coming form
// your API to a valid Pipes2 JSON.
// If the function returns errors please make sure you pass
// them back in the response with the relevant http status code.
function renderEpisodeFeeds(modelResponse) {
// lets say the searchEpisodes returns the following array
// of episodes:
// {
// title: "Show title"
// items: [
// {
// id: "episode-98723",
// title: "episode name",
// description: "episode description",
// image: "https://example.com/images/episode-98723.png",
// episodeNumber: 1,
// source: "https://example.com/episode-98723.h3u8"
// },
// ...
// ]
// }
return {
id: `/search`,
title: modelResponse.title,
entry: modelResponse.items((episode) => {
return {
id: episode.id,
title: episode.title,
summary: episode.description,
type: {
// Tapping on the item in the device will
// open the screen that is type mapped to `video` in Zapp studio.
value: "video",
},
content: {
type: "video/hls",
src: episode.source,
},
media_group: [
{
type: "image",
media_item: [
{
src: episode.image,
key: "image_base",
},
],
},
],
};
}),
};
}

res
.set("Content-Type", "application/vnd+applicaster.pipes2+json")
.send(renderEpisodeFeeds(episodes));
});

Protecting content streams

In cases you would like to serve the stream only for users that bought that specific content, do the following:

Add the requires_authentication extension and set it to true - adding it will trigger the app's configured login flow if the user is not logged it.

Remove the content field from the feed response. Instead, create a second feed that will check the user permissions before playing the video. Set the URL of that feed in the entry's link.href field as seen in the example bellow.

note

Don't forget to set the endpoint of the video feed (in this example /episodes/) with the bearer token context key set to quick-brick-login-flow.access_token.

For more information on context keys see

Note that you can use the ctx query param or any other available context key type instead of bearer token if you prefer to do so.

For example, here is a feed with a few episodes. As you can see, the episodes don't have the content feed with the source set. Instead it has a link field that holds a link to the specific episode feed (A feed with only one entry).


import getEpisodesByShowId from "./models/episodes";

app.get("/shows/:showId", async (req, res) => {
const episodesByShowId = await getEpisodesByShowId(req.params.showId);

function renderEpisodeFeeds(modelResponse) {
// Lets say the getEpisodesByShowId returns the following array
// of episodes:
// {
// title: "Show title"
// items: [
// {
// id: "episode-98723",
// title: "episode name",
// description: "episode description",
// image: "https://example.com/images/episode-98723.png",
// episodeNumber: 1,
// source: "https://example.com/episode-98723.h3u8"
// },
// ...
// ]
// }
return {
id: `/shows/${req.params.showId}`,
title: modelResponse.title,
entry: modelResponse.items((episode) => {
return {
id: episode.id,
title: episode.title,
// Instead of the commented out content field
// pass the URL for a feed of that specific episode in the
// link field.
// The feed will be called by the app before the item
// is played.
link: {
href: `https://example.com/api/episodes/${episode.id}`,
rel: "self",
},
summary: episode.description,
type: {
// Tapping on the item in the device will
// open the screen that is type mapped to `video` in Zapp studio.
value: "video",
},
// The content field is not rendered
// content: {
// type: "video/hls",
// src: episode.source,
// },
media_group: [
{
type: "image",
media_item: [
{
src: episode.image,
key: "image_base",
},
],
},
],
extensions: {
requires_authentication: true
}
};
}),
};
}

Now create the feed for the specific episode:


// Here is the feed of the specific episode that is retrieved
// before the player plays the episode's video.
// The token will be passed in as part of the request as a bearer header
// or as a query param by setting the `/episodes` endpoint to send the user token.
// This configuration is done inside Zapp Studio UI.
app.get("/episodes/:episodeId", async (req, res) => {
const bearerToken = req.headers['authorization'];
// If you chose to set the context key as a `ctx` type instead of header
// uncomment the following and comment out the line above.

// const ctxObj = JSON.parse(base64url.decode(req.query.ctx));
// const bearerToken = ctxObj[quick-brick-login-flow.access_token'];


if (typeof bearerToken !== undefined) {
const token = bearerToken.split(' ')[1];
// Implement checkPermission function to check if the user of the token
// is authorized to see the episode with that episodeId.
// It's common that the token is a JWT token.
// This way you can retrieve the user's info from it and check if
// he/she is entitled to see the episode.
const isAuthorized = await checkPermission(token, req.params.episodeId)
if (!isAuthorized) res.sendStatus(403)
} else {
res.sendStatus(405)
}

const episode = await getEpisodesById(req.params.episodeId);


return {
id: `${episode.id}-feed}`,
// The feed will have one entry with the entry of the episode
entry: [
{
id: episode.id,
title: episode.title,
summary: episode.description,
type: {
// Tapping on the item in the device will
// open the screen that is type mapped to `video` in Zapp studio.
value: "video",
},
// The content field is now rendered.
content: {
type: "video/hls",
src: episode.source,
},
media_group: [
{
type: "image",
media_item: [
{
src: episode.image,
key: "image_base",
},
],
},
],
extensions: {
requires_authentication: true
}
},
],
};
});

res
.set("Content-Type", "application/vnd+applicaster.pipes2+json")
.send(renderEpisodeFeeds(episodesByShowId));
});

Passing other context keys to the request

Note that you can pass other context keys in feed requests to manipulate the feed response for a specific set of users.

For example by passing the languageCode context key you can set the translation of the feed to the user's language.

You can check the available context keys here.