How to parse a M3U file from React Native Expo

252 Views Asked by At

How would I parse the content like the Title and URL from a M3U file in React Native Expo?

1

There are 1 best solutions below

0
iamMAHAM On

If you are using nodejs you can use this package here. But in react native it does not work. I edited a main file to make work with react-native. You can create a file and paste code inside : if you are using javascript you can juste remove all types

export const ipTvParser = (content: string) => {
  const lines = content
    .split('\n')
    .map((line, index) => ({ index, raw: line }));

  const playlist: Playlist = {
    // @ts-expect-error
    header: {},
    items: [],
  };

  const firstLine = lines.find((l: { index: number }) => l.index === 0);

  if (!firstLine || !/^#EXTM3U/.test(firstLine.raw))
    throw new Error('Playlist is not valid');

  playlist.header = parseHeader(firstLine);

  let i = 0;
  const items: Record<string, PlaylistItem> = {};
  for (const line of lines) {
    if (line.index === 0) continue;
    const string = line.raw.trim();

    if (string.startsWith('#EXTINF:')) {
      const EXTINF = string;
      items[i] = {
        name: getName(EXTINF),
        tvg: {
          id: getAttribute(EXTINF, 'tvg-id'),
          name: getAttribute(EXTINF, 'tvg-name'),
          logo: getAttribute(EXTINF, 'tvg-logo'),
          url: getAttribute(EXTINF, 'tvg-url'),
          rec: getAttribute(EXTINF, 'tvg-rec'),
          shift: getAttribute(EXTINF, 'tvg-shift'),
        },
        group: {
          title: getAttribute(EXTINF, 'group-title'),
        },
        http: {
          referrer: '',
          'user-agent': getAttribute(EXTINF, 'user-agent'),
        },
        url: 'undefined',
        raw: line.raw,
        line: line.index + 1,
        catchup: {
          type: getAttribute(EXTINF, 'catchup'),
          days: getAttribute(EXTINF, 'catchup-days'),
          source: getAttribute(EXTINF, 'catchup-source'),
        },
        timeshift: getAttribute(EXTINF, 'timeshift'),
      };
    } else if (string.startsWith('#EXTVLCOPT:')) {
      if (!items[i]) continue;
      const EXTVLCOPT = string;
      items[i].http.referrer =
        getOption(EXTVLCOPT, 'http-referrer') || items[i].http.referrer;
      items[i].http['user-agent'] =
        getOption(EXTVLCOPT, 'http-user-agent') || items[i].http['user-agent'];
      items[i].raw += `\r\n${line.raw}`;
    } else if (string.startsWith('#EXTGRP:')) {
      if (!items[i]) continue;
      const EXTGRP = string;
      items[i].group.title = getValue(EXTGRP) || items[i].group.title;
      items[i].raw += `\r\n${line.raw}`;
    } else {
      if (!items[i]) continue;
      const url = getURL(string);
      const user_agent = getParameter(string, 'user-agent');
      const referrer = getParameter(string, 'referer');
      if (url) {
        items[i].url = url;
        items[i].http['user-agent'] = user_agent || items[i].http['user-agent'];
        items[i].http.referrer = referrer || items[i].http.referrer;
        items[i].raw += `\r\n${line.raw}`;
        i++;
      } else {
        if (!items[i]) continue;
        items[i].raw += `\r\n${line.raw}`;
      }
    }
  }

  playlist.items = Object.values(items);

  return playlist;
};

const parseHeader = (line: any) => {
  const supportedAttrs = ['x-tvg-url', 'url-tvg'] as const;

  // @ts-expect-error
  const attrs: Record<(typeof supportedAttrs)[number], string> = {};
  for (const attrName of supportedAttrs) {
    const tvgUrl = getAttribute(line.raw, attrName);
    if (tvgUrl) {
      attrs[attrName] = tvgUrl;
    }
  }

  return {
    attrs,
    raw: line.raw,
  };
};

const getName = (string: string) => {
  const info = string.replace(/="(.*?)"/g, '');
  const parts = info.split(/,(.*)/);

  return parts[1] || '';
};

const getAttribute = (string: string, name: string) => {
  const regex = new RegExp(name + '="(.*?)"', 'gi');
  const match = regex.exec(string);

  return match?.[1] ? match[1] : '';
};

const getOption = (string: string, name: string) => {
  const regex = new RegExp(':' + name + '=(.*)', 'gi');
  const match = regex.exec(string);

  return match?.[1] && typeof match[1] === 'string'
    ? match[1].replace(/"/g, '')
    : '';
};

const getValue = (string: string) => {
  const regex = new RegExp(':(.*)', 'gi');
  const match = regex.exec(string);

  return match?.[1] && typeof match[1] === 'string'
    ? match[1].replace(/"/g, '')
    : '';
};

const getURL = (string: string) => {
  return string.split('|')[0] || '';
};

const getParameter = (string: string, name: string) => {
  const params = string.replace(/^(.*)\|/, '');
  const regex = new RegExp(name + '=(\\w[^&]*)', 'gi');
  const match = regex.exec(params);

  return match?.[1] ? match[1] : '';
};

export interface PlaylistHeader {
  attrs: {
    'x-tvg-url': string;
  };
  raw: string;
}

export interface PlaylistItemTvg {
  id: string;
  name: string;
  url: string;
  logo: string;
  rec: string;
  shift: string;
}

export interface PlaylistItem {
  name: string;
  tvg: PlaylistItemTvg;
  group: {
    title: string;
  };
  http: {
    referrer: string;
    'user-agent': string;
  };
  url: string;
  raw: string;
  line: number;
  timeshift: string;
  catchup: {
    type: string;
    source: string;
    days: string;
  };
}

export interface Playlist {
  header: PlaylistHeader;
  items: PlaylistItem[];
}