> ## Documentation Index
> Fetch the complete documentation index at: https://docs.livepeer.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Build Livepeer

> Ways builders can contribute code, tooling, and ecosystem projects to Livepeer.

export const DynamicTableV2 = ({tableTitle = null, headerList = [], itemsList = [], monospaceColumns = [], columnWidths = {}, columnConfig = {}, showSeparators = false, margin, className = '', style = {}, ...rest}) => {
  if (!headerList.length) {
    return <div>No headers provided</div>;
  }
  const tableRef = useRef(null);
  const [measuredColumnWidths, setMeasuredColumnWidths] = useState({});
  const measureFitColumns = () => {
    const tableElement = tableRef.current;
    if (!tableElement) {
      return;
    }
    const nextWidths = headerList.reduce((accumulator, header, index) => {
      const config = columnConfig?.[header] || ({});
      if (!config.fitContent) {
        return accumulator;
      }
      const contentNodes = tableElement.querySelectorAll(`[data-docs-column-key="${index}"] [data-docs-fit-content]`);
      let maxContentWidth = 0;
      contentNodes.forEach(node => {
        const width = Math.ceil(node.getBoundingClientRect().width);
        if (width > maxContentWidth) {
          maxContentWidth = width;
        }
      });
      if (maxContentWidth > 0) {
        accumulator[header] = `${maxContentWidth + 16}px`;
      }
      return accumulator;
    }, {});
    setMeasuredColumnWidths(currentWidths => {
      const currentEntries = Object.entries(currentWidths);
      const nextEntries = Object.entries(nextWidths);
      if (currentEntries.length === nextEntries.length && nextEntries.every(([header, width]) => currentWidths[header] === width)) {
        return currentWidths;
      }
      return nextWidths;
    });
  };
  useLayoutEffect(() => {
    measureFitColumns();
  }, [headerList, itemsList, columnConfig]);
  useEffect(() => {
    const tableElement = tableRef.current;
    if (!tableElement || typeof ResizeObserver === 'undefined') {
      return undefined;
    }
    const resizeObserver = new ResizeObserver(() => {
      measureFitColumns();
    });
    resizeObserver.observe(tableElement);
    if (tableElement.parentElement) {
      resizeObserver.observe(tableElement.parentElement);
    }
    return () => {
      resizeObserver.disconnect();
    };
  }, [headerList, itemsList, columnConfig]);
  const fitHeaders = headerList.filter(header => columnConfig?.[header]?.fitContent);
  const hasMeasuredFitColumns = fitHeaders.length === 0 || fitHeaders.every(header => Boolean(measuredColumnWidths[header]));
  const getColumnStyle = (header, isMonospace = false) => {
    const config = columnConfig?.[header] || ({});
    const fitContent = Boolean(config.fitContent);
    const fluid = Boolean(config.fluid);
    const nowrap = Boolean(config.nowrap) || fitContent || isMonospace;
    const preferredWidth = columnWidths[header];
    const measuredWidth = measuredColumnWidths[header];
    return {
      ...fitContent && measuredWidth ? {
        width: measuredWidth,
        minWidth: measuredWidth,
        maxWidth: measuredWidth
      } : {},
      ...!fitContent && !fluid && preferredWidth ? {
        minWidth: preferredWidth
      } : {},
      ...nowrap ? {
        whiteSpace: 'nowrap'
      } : {
        wordWrap: 'break-word',
        overflowWrap: 'break-word'
      }
    };
  };
  const getColumnTrackStyle = header => {
    const config = columnConfig?.[header] || ({});
    const fitContent = Boolean(config.fitContent);
    const fluid = Boolean(config.fluid);
    const preferredWidth = columnWidths[header];
    const measuredWidth = measuredColumnWidths[header];
    if (fitContent && measuredWidth) {
      return {
        width: measuredWidth,
        minWidth: measuredWidth,
        maxWidth: measuredWidth
      };
    }
    if (fluid) {
      return {};
    }
    if (preferredWidth) {
      return {
        width: preferredWidth
      };
    }
    return {};
  };
  const renderCellContent = (header, content) => {
    const config = columnConfig?.[header] || ({});
    if (!config.fitContent) {
      return content;
    }
    return <div data-docs-fit-content style={{
      display: 'inline-flex',
      alignItems: 'center',
      whiteSpace: 'nowrap',
      width: 'max-content',
      maxWidth: 'none'
    }}>
        {content}
      </div>;
  };
  return <div className={className} style={style} {...rest}>
      {tableTitle && <div style={{
    fontStyle: 'italic',
    margin: 0
  }}>
          <strong>{tableTitle}</strong>
        </div>}
      <div style={{
    overflowX: 'auto',
    ...margin != null && ({
      margin
    })
  }} role="region" tabIndex={0} aria-label={tableTitle ? `Scrollable table: ${tableTitle}` : 'Scrollable table'}>
        <table ref={tableRef} data-docs-dynamic-table-v2 style={{
    width: '100%',
    tableLayout: hasMeasuredFitColumns ? 'fixed' : 'auto',
    borderCollapse: 'collapse',
    fontSize: '0.9rem',
    marginTop: 0
  }}>
          <colgroup>
            {headerList.map((header, index) => <col key={index} style={getColumnTrackStyle(header)} />)}
          </colgroup>
          <thead>
            <tr style={{
    backgroundColor: 'var(--lp-color-accent)',
    color: 'var(--lp-color-on-accent)',
    borderBottom: '1px solid var(--lp-color-border-default)'
  }}>
              {headerList.map((header, index) => <th key={index} data-docs-column-key={index} style={{
    padding: '10px 8px',
    textAlign: 'left',
    fontWeight: '600',
    color: 'var(--lp-color-on-accent)',
    verticalAlign: 'top',
    ...getColumnStyle(header)
  }}>
                  {renderCellContent(header, header)}
                </th>)}
            </tr>
          </thead>
          <tbody>
            {itemsList.filter(item => showSeparators || !item?.__separator).map((item, rowIndex) => item?.__separator ? <tr key={rowIndex} style={{
    backgroundColor: 'var(--lp-color-accent)',
    color: 'var(--lp-color-on-accent)',
    borderBottom: '1px solid var(--lp-color-accent)'
  }}>
                    <td colSpan={headerList.length} style={{
    padding: '6px 8px',
    fontWeight: '700',
    color: 'var(--lp-color-on-accent)',
    letterSpacing: '0.01em'
  }}>
                      {(item[headerList[0]] ?? item.Category) ?? 'Category'}
                    </td>
                  </tr> : <tr key={rowIndex} style={{
    borderBottom: '1px solid var(--lp-color-border-default)'
  }}>
                    {headerList.map((header, colIndex) => {
    const value = (item[header] ?? item[header.toLowerCase()]) ?? '-';
    const isMonospace = monospaceColumns.includes(colIndex);
    return <td key={colIndex} data-docs-column-key={colIndex} style={{
      padding: '8px 8px',
      fontFamily: isMonospace ? 'monospace' : 'inherit',
      verticalAlign: 'top',
      ...getColumnStyle(header, isMonospace)
    }}>
                          {renderCellContent(header, isMonospace ? <code>{value}</code> : value)}
                        </td>;
  })}
                  </tr>)}
          </tbody>
        </table>
      </div>
    </div>;
};

export const CustomDivider = ({color = "var(--lp-color-border-default)", middleText = "", spacing = "default", style = {}, className = "", ...rest}) => {
  const spacingPresets = {
    default: {
      margin: "24px 0"
    },
    overlap: {
      margin: "-1rem 0 -1rem 0"
    },
    tight: {
      margin: "0 0 -1rem 0"
    },
    section: {
      margin: "0 0 -2rem 0"
    },
    sectionOverlap: {
      margin: "-1rem 0 -2rem 0"
    },
    deepOverlap: {
      margin: "-1rem 0 -1.5rem 0"
    }
  };
  const spacingStyle = spacingPresets[spacing] || spacingPresets.default;
  return <div role="separator" aria-orientation="horizontal" className={className} style={{
    display: "flex",
    alignItems: "center",
    ...spacingStyle,
    fontSize: style?.fontSize || "16px",
    height: "fit-content",
    ...style
  }} {...rest}>
      <span style={{
    marginRight: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
      </span>
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      {middleText && <>
          <Icon icon="circle" size={2} />
          <span style={{
    margin: "0 8px",
    fontWeight: "bold",
    color: color,
    opacity: 0.7
  }}>
            {middleText}
          </span>
          <Icon icon="circle" size={2} />
        </>}
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      <span style={{
    marginLeft: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <span style={{
    display: "inline-block",
    transform: "scaleX(-1)"
  }}>
          <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
        </span>
      </span>
    </div>;
};

Livepeer is open-source infrastructure for video transcoding and AI compute. All core protocol code, SDKs, and tooling are publicly available on GitHub.

## Core Repositories

<CardGroup cols={2}>
  <Card title="go-livepeer" icon="golang" href="https://github.com/livepeer/go-livepeer">
    Core protocol node - Orchestrator and Gateway implementation in Go.
  </Card>

  <Card title="ai-worker / ai-runner" icon="microchip" href="https://github.com/livepeer/ai-worker">
    AI inference pipeline - Python framework for real-time and batch AI video processing.
  </Card>

  <Card title="livepeer-js" icon="js" href="https://github.com/livepeer/livepeer-js">
    TypeScript/JavaScript SDK and React UI components for building video applications.
  </Card>

  <Card title="ComfyStream" icon="wand-magic-sparkles" href="https://github.com/yondonfu/comfystream">
    Real-time AI video pipelines using ComfyUI - the foundation for AI Video SPE work.
  </Card>
</CardGroup>

<CustomDivider />

## SDKs & Libraries

<DynamicTableV2
  headerList={["SDK", "Language", "Maintained By"]}
  itemsList={[
{ SDK: "[livepeer-js](https://github.com/livepeer/livepeer-js)", Language: "TypeScript/JavaScript", "Maintained By": "Livepeer Inc" },
{ SDK: "[@livepeer/react](https://github.com/livepeer/livepeer-js)", Language: "React components", "Maintained By": "Livepeer Inc" },
{ SDK: "[livepeer-ai-js](https://github.com/livepeer/ai-worker)", Language: "TypeScript (AI)", "Maintained By": "Livepeer Inc" },
{ SDK: "[livepeer-ai-python](https://github.com/livepeer/ai-worker)", Language: "Python (AI)", "Maintained By": "Livepeer Inc" },
{ SDK: "[livepeer-go](https://github.com/livepeer/go-livepeer)", Language: "Go", "Maintained By": "Livepeer Inc" },
{ SDK: "[PyTrickle](https://github.com/livepeer/pytrickle)", Language: "Python (BYOC framework)", "Maintained By": "Livepeer Inc" },
{ SDK: "[livepeer-python-gateway](https://github.com/j0sh/livepeer-python-gateway)", Language: "Python (gateway client)", "Maintained By": "Community (j0sh)" },
{ SDK: "[Bubble Plugin](https://bubble.io)", Language: "No-code", "Maintained By": "Community" }
]}
/>

<CustomDivider />

## Where to Contribute

**Protocol and infrastructure** - Issues on [go-livepeer](https://github.com/livepeer/go-livepeer/issues) and [ai-worker](https://github.com/livepeer/ai-worker/issues). Core R\&D is coordinated through bi-weekly Dev Calls on Discord.

**Developer tools** - The [awesome-Livepeer](https://github.com/livepeer/awesome-livepeer) directory tracks community-built tools. If you build something useful, submit a PR to add it.

**AI video workflows** - The AI Video SPE funds ComfyStream and BYOC container work. Check the [Dev Hub](https://www.livepeer.org/dev-hub) for AI Workflow Grants.

**Documentation** - The [docs repository](https://github.com/livepeer/docs) accepts pull requests. Pages are written in MDX.

<CustomDivider />

## Funded Work

Builders can apply for grants through the [Dev Hub](https://www.livepeer.org/dev-hub) or propose larger-scoped work as an SPE through [on-chain governance](/v2/community/ecosystem/governance).

<DynamicTableV2
  headerList={["Path", "Scope", "How to Apply"]}
  itemsList={[
{ Path: "Microgrants", Scope: "1-month, tightly scoped", "How to Apply": "[Dev Hub](https://www.livepeer.org/dev-hub)" },
{ Path: "Supply Side Grants", Scope: "Orchestrator/delegator tooling", "How to Apply": "[Dev Hub](https://www.livepeer.org/dev-hub)" },
{ Path: "AI Workflow Grants", Scope: "ComfyUI/ComfyStream workflows", "How to Apply": "[Dev Hub](https://www.livepeer.org/dev-hub)" },
{ Path: "SPE Treasury Proposal", Scope: "Community-initiated, governance-approved", "How to Apply": "[Forum](https://forum.livepeer.org/c/governance)" },
{ Path: "Bug Bounty", Scope: "Security vulnerability reports", "How to Apply": "[Immunefi](https://immunefi.com)" }
]}
/>

For full details on all opportunities, see [Opportunities](/v2/community/contribute/opportunities).
