Skip to content

Commit 6d6710f

Browse files
authored
Merge branch 'master' into migrate-to-github-actions
2 parents a0bcbef + 4b57e56 commit 6d6710f

7 files changed

Lines changed: 131 additions & 21 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
{
2-
"name": "membrane-framework",
3-
"description": "Membrane multimedia streaming framework skill for Elixir — provides architectural guidance, callback and action references, and code patterns for building pipelines, elements, and bins with membrane_core.",
2+
"name": "membrane",
3+
"description": "Skills for Membrane multimedia streaming and processing framework in Elixir.",
44
"owner": {
55
"name": "Software Mansion",
66
"url": "https://github.com/membraneframework"
77
},
88
"plugins": [
99
{
10-
"name": "membrane-core",
11-
"description": "Work with the Membrane multimedia streaming framework in Elixir. Provides architectural guidance, callback/action references, and code patterns for pipelines, elements, and bins.",
12-
"version": "1.0.0",
10+
"name": "membrane-framework",
11+
"description": "AI coding skill for the Membrane Framework in Elixir. Build or debug Membrane multimedia processing and streaming pipelines and write custom Membrane Elements, Bins, and Filters.",
12+
"version": "0.1.0",
1313
"source": "./",
1414
"category": "development",
1515
"homepage": "https://github.com/membraneframework/membrane_core",
16-
"skills": [
17-
"./skills/membrane-framework"
18-
]
16+
"skills": ["./skills/membrane-framework"]
1917
}
2018
]
2119
}

.claude-plugin/plugin.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "membrane-framework",
3+
"description": "AI coding skill for the Membrane Framework in Elixir. Build or debug Membrane multimedia processing and streaming pipelines and write custom Membrane Elements, Bins, and Filters.",
4+
"version": "0.1.0",
5+
"author": {
6+
"name": "Software Mansion",
7+
"url": "https://github.com/membraneframework"
8+
},
9+
"homepage": "https://github.com/membraneframework/membrane_core",
10+
"skills": ["./skills/membrane-framework"]
11+
}

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ You can also [follow Membrane on X (Twitter)](https://twitter.com/ElixirMembrane
2121

2222
If you already had a chance to use Membrane, we will be greateful if could fill out quick [survey](https://forms.gle/dgVDFHUD7CUGxU5VA) to help us improve framework and decide on what to do next.
2323

24+
For people who write Membrane code with AI assistants, we ship a [dedicated skill](#membrane-skill-for-ai-assistants) for them.
25+
2426
Membrane is maintained by [Software Mansion](https://swmansion.com).
2527

2628
## Quick start
@@ -71,6 +73,27 @@ To learn step-by-step what exactly happens here, follow [this tutorial](https://
7173

7274
The best place to learn Membrane is the [membrane.stream/learn](https://membrane.stream/learn) website and the [membrane_demo](https://github.com/membraneframework/membrane_demo) repository. Try them out, then hack something exciting!
7375

76+
## Membrane skill for AI assistants
77+
78+
This repo includes [`skills/membrane-framework/SKILL.md`](skills/membrane-framework/SKILL.md), a structured guide that helps AI coding assistants write Membrane code.
79+
80+
If you use [Claude Code](https://docs.claude.com/en/docs/claude-code), install it via the plugin system:
81+
82+
```
83+
/plugin marketplace add membraneframework/membrane_core
84+
/plugin install membrane-framework@membrane
85+
```
86+
87+
After install, Claude automatically pulls in the skill whenever you work on Membrane code (mentioning `Membrane.Pipeline`, `Bin`, `Filter`, `Pad`, etc).
88+
89+
If you use [Cursor](https://www.cursor.com/), drop the skill into your project's rules directory:
90+
91+
```
92+
mkdir -p .cursor/rules && curl -fL https://raw.githubusercontent.com/membraneframework/membrane_core/master/skills/membrane-framework/SKILL.md -o .cursor/rules/membrane-framework.mdc
93+
```
94+
95+
For other agents, include the `SKILL.md` file directly in the agent's context. If your tool doesn't support that, copy the file's contents into whatever instruction file it reads.
96+
7497
## Structure of the framework
7598

7699
The most basic media processing entities of Membrane are `Element`s. An element might be able, for example, to mux incoming audio and video streams into MP4, or play raw audio using your sound card. You can create elements yourself, or choose from the ones provided by the framework.

lib/membrane/core/subprocess_supervisor.ex

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ defmodule Membrane.Core.SubprocessSupervisor do
139139
name: {__MODULE__, name},
140140
type: :supervisor,
141141
child_pid: child_pid,
142-
role: :subprocess_supervisor
142+
role: :subprocess_supervisor,
143+
pending_child_death: nil
143144
})
144145

145146
{:reply, {:ok, child_pid}, state}
@@ -221,7 +222,7 @@ defmodule Membrane.Core.SubprocessSupervisor do
221222

222223
defp do_handle_info({:EXIT, pid, reason}, state) do
223224
{data, state} = pop_in(state, [:children, pid])
224-
handle_exit(data, reason, state)
225+
state = handle_exit(data, reason, state)
225226

226227
case state do
227228
%{parent_process: :exit_requested, children: children} when children == %{} ->
@@ -245,27 +246,33 @@ defmodule Membrane.Core.SubprocessSupervisor do
245246
end
246247

247248
defp handle_exit(%{role: :subprocess_supervisor} = data, reason, state) do
248-
case Map.fetch(state.children, data.child_pid) do
249+
with :error <- Map.fetch(state.children, data.child_pid),
250+
%{pending_child_death: {child_name, death_reason}} <- data do
251+
Message.send(state.parent_component, :child_death, [child_name, death_reason])
252+
else
249253
{:ok, child_data} ->
250254
raise "Subprocess supervisor failure #{inspect(child_data.name)}, reason: #{inspect(reason)}"
251255

252-
:error ->
256+
_no_pending_death ->
253257
:ok
254258
end
259+
260+
state
255261
end
256262

257263
defp handle_exit(%{role: :component} = data, reason, state) do
258264
Process.exit(data.supervisor_pid, :shutdown)
259-
Message.send(state.parent_component, :child_death, [data.name, reason])
265+
266+
put_in(state.children[data.supervisor_pid].pending_child_death, {data.name, reason})
260267
end
261268

262-
defp handle_exit(%{role: :utility}, _reason, _state) do
263-
:ok
269+
defp handle_exit(%{role: :utility}, _reason, state) do
270+
state
264271
end
265272

266273
# Clause handling the case when child start function returns error
267274
# and we don't know its PID, but we still receive exit signal from it.
268-
defp handle_exit(nil, _reason, _state) do
269-
:ok
275+
defp handle_exit(nil, _reason, state) do
276+
state
270277
end
271278
end

mix.exs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,19 +108,31 @@ defmodule Membrane.Mixfile do
108108
Membrane.RCMessage
109109
],
110110
groups_for_modules: groups_for_modules(),
111-
groups_for_extras: groups_for_extras()
111+
groups_for_extras: groups_for_extras(),
112+
before_closing_head_tag: &before_closing_head_tag/1
112113
]
113114
end
114115

116+
# Hides the AI skill page from the sidebar while keeping it published and linked from llms.txt.
117+
defp before_closing_head_tag(:html) do
118+
"""
119+
<style>
120+
#sidebar li:has(> a[href$="skill.html"]),
121+
#sidebar a[href$="skill.html"] { display: none; }
122+
</style>
123+
"""
124+
end
125+
126+
defp before_closing_head_tag(_format), do: ""
127+
115128
defp packages_in_ecosystem do
116129
{packages, _bindings} = Code.eval_file(@hex_packages_path)
117130
packages
118131
end
119132

120133
defp extras do
121134
[
122-
{"skills/membrane-framework/SKILL.md",
123-
[title: "Membrane Framework AI Skill", hidden: true]},
135+
{"skills/membrane-framework/SKILL.md", [title: "Membrane Framework AI Skill"]},
124136
"README.md",
125137
{"guides/llms/packages_list.md",
126138
[title: "Packages in the Membrane ecosystem", hidden: true]},

skills/membrane-framework/SKILL.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
---
22
name: membrane-framework
33
description: Work with the Membrane multimedia streaming framework in Elixir. Use this skill whenever the user is building or debugging Membrane pipelines, writing custom Elements, Bins, or Filters, connecting pads, implementing callbacks, handling stream formats or EOS, or asking about Membrane architecture. Trigger on any mention of membrane_core, membrane, membrane framework, Membrane.Pipeline, Membrane.Sink, Membrane.Source, Membrane.Filter, Membrane.Endpoint, Membrane.Bin, Membrane.Pad, or multimedia streaming in an Elixir context — even if the user doesn't say "Membrane" explicitly but is clearly working on this codebase.
4+
globs: ["**/*.ex", "**/*.exs"]
5+
alwaysApply: false
46
---
57

68
# Membrane Framework
79

8-
**Package**: `membrane_core` ~> 1.2 | **Docs**: https://hexdocs.pm/membrane_core/ | **Module index**: https://hexdocs.pm/membrane_core/llms.txt | **Demos**: https://github.com/membraneframework/membrane_demo | **All packages**: [packages_list.md](../../guides/llms/packages_list.md)
10+
**Package**: `membrane_core` ~> 1.3 | **Docs**: https://hexdocs.pm/membrane_core/ | **Module index**: https://hexdocs.pm/membrane_core/llms.txt | **Demos**: https://github.com/membraneframework/membrane_demo | **All packages**: [packages_list.md](../../guides/llms/packages_list.md)
911

1012
## How to Approach Tasks
1113

test/membrane/integration/callbacks_test.exs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,61 @@ defmodule Membrane.Integration.CallbacksTest do
121121
{:DOWN, _ref, _process, ^pipeline, _reason} -> :ok
122122
end
123123
end
124+
125+
defmodule BinWithTwoChildren do
126+
use Membrane.Bin
127+
128+
alias Membrane.Integration.CallbacksTest.PadlessElement
129+
130+
@impl true
131+
def handle_init(_ctx, _opts) do
132+
{[spec: [child(:child_a, PadlessElement), child(:child_b, PadlessElement)]], %{}}
133+
end
134+
135+
@impl true
136+
def handle_playing(ctx, state) do
137+
{[notify_parent: {:children_pids, ctx.children.child_a.pid, ctx.children.child_b.pid}],
138+
state}
139+
end
140+
end
141+
142+
defmodule BinTerminationPipeline do
143+
use Membrane.Pipeline
144+
145+
@impl true
146+
def handle_child_notification({:children_pids, a_pid, b_pid}, :bin, _ctx, state) do
147+
{[], %{state | a_pid: a_pid, b_pid: b_pid}}
148+
end
149+
150+
@impl true
151+
def handle_child_terminated(:bin, _ctx, state) do
152+
send(
153+
state.test_pid,
154+
{:terminated, Process.alive?(state.a_pid), Process.alive?(state.b_pid)}
155+
)
156+
157+
{[], state}
158+
end
159+
end
160+
161+
test "handle_child_terminated fires only after bin's subprocess_supervisor dies, ensuring bin's children are already dead" do
162+
pipeline =
163+
Testing.Pipeline.start_link_supervised!(
164+
module: BinTerminationPipeline,
165+
custom_args: %{test_pid: self(), a_pid: nil, b_pid: nil}
166+
)
167+
168+
Testing.Pipeline.execute_actions(pipeline,
169+
spec: {child(:bin, BinWithTwoChildren), group: :bin_group, crash_group_mode: :temporary}
170+
)
171+
172+
assert_pipeline_notified(pipeline, :bin, {:children_pids, _a_pid, _b_pid})
173+
174+
Testing.Pipeline.get_child_pid!(pipeline, :bin)
175+
|> Process.exit(:crash)
176+
177+
assert_receive {:terminated, false, false}, 1000
178+
179+
Testing.Pipeline.terminate(pipeline)
180+
end
124181
end

0 commit comments

Comments
 (0)