A developer's honest account of building OpVoc: the technical decisions, the mistakes, the lessons, and why voice is the future of social media.
The Idea I've been thinking about voice social media for a long time. The concept is obvious once you see the gap: every major social platform is built around text or video. Nobody has built the Twitter of voice — a place where you post a short audio clip, build a following, and connect with people through your actual voice. So I built it. This is the story of how, what I learned, and what I'd do differently.
The Tech Stack — And Why I Chose It Backend: PHP 8 with PDO/MySQL Frontend: Vanilla JavaScript, no framework Database: MySQL on shared hosting Hosting: Namecheap shared hosting (for now) Payments: PayPal SDK (live, not sandbox) I know what you're thinking. PHP in 2025? On shared hosting? Yes. And here's why it was the right call. The goal was to ship fast, keep costs near zero, and validate the concept before investing in infrastructure. PHP runs natively on virtually every shared host in the world. No Docker. No containers. No deployment pipeline. Upload files, done. Facebook started on PHP. Instagram launched on a $20/month server. The technology is not the product. That said, if OpVoc reaches 50,000+ active users, we'll move to a VPS and eventually a proper cloud setup. But that's a good problem to have.
Database Architecture The core tables: sqlusers — id, username, email, password, display_name, bio, avatar, subscribers, theme, is_pro, lat, lng, city, is_admin, is_banned, shadow_ban, is_muted
podcasts — id, user_id, title, description, audio_file, duration, privacy, plays, likes_count, is_anonymous, lat, lng, city, reports_count, is_suppressed, sponsored_until, promo_link, is_starter, starter_label
stories — id, user_id, audio_file, duration, caption, plays, expires_at
comments — id, user_id, podcast_id, content, edited comment_replies — id, comment_id, user_id, content, edited comment_likes — user_id, comment_id
likes — user_id, podcast_id reposts — user_id, podcast_id bookmarks — user_id, podcast_id subscriptions — subscriber_id, target_id
notifications — id, user_id, from_id, type, podcast_id, comment_id, is_read messages — id, sender_id, receiver_id, content, audio_file, is_read
reports — user_id, podcast_id, reason sponsored_posts — user_id, podcast_id, budget, daily_budget, plays_used, active, ends_at The schema is normalized but not over-engineered. For the scale we're at, this is more than sufficient.
Audio Handling — The Tricky Part Audio files are the core of the product. Here's how we handle them: Upload flow:
Client sends multipart/form-data with audio file PHP validates MIME type against whitelist: audio/mpeg, audio/wav, audio/ogg, audio/mp4, audio/x-m4a, audio/aac File size check — max 10MB for posts, 5MB for stories move_uploaded_file() to /uploads/audio/ with unique filename Duration check via ffprobe (falls back to 0 on shared hosting without ffprobe) Database insert with file path, duration, and metadata
The ffprobe problem: Shared hosting doesn't have ffprobe installed. This means we can't reliably get audio duration server-side. Our workaround: store duration as 0 when ffprobe isn't available, and rely on the HTML5 Audio API to get duration client-side for display. Browser recording: We use the MediaRecorder API for in-browser voice recording. The recorded blob is audio/webm — which works in Chrome, Firefox, and modern Safari. We auto-stop at 60 seconds. javascriptnavigator.mediaDevices.getUserMedia({audio: true}).then(stream => { const recorder = new MediaRecorder(stream); recorder.ondataavailable = e => chunks.push(e.data); recorder.onstop = () => { const blob = new Blob(chunks, {type: 'audio/webm'}); // inject into form as File object const file = new File([blob], 'voice-' + Date.now() + '.webm'); const dt = new DataTransfer(); dt.items.add(file); audioInput.files = dt.files; }; recorder.start(); });
The Features That Matter Most Building a social network means building a lot of features before the product feels complete. Here's what actually moved the needle: Anonymous posting — This was an afterthought that became essential. When people can post without their name attached, they post more honestly. The content is better. The engagement is higher. Nearby Voices — Using the Haversine formula on lat/lng coordinates stored per post, we show content from within 50km by default. This creates a local discovery layer that feels completely different from algorithm-driven global feeds. sqlSELECT p.*, (6371 * ACOS( COS(RADIANS(?)) * COS(RADIANS(p.lat)) * COS(RADIANS(p.lng) - RADIANS(?)) + SIN(RADIANS(?)) * SIN(RADIANS(p.lat)) )) AS distance_km FROM podcasts p HAVING distance_km <= 50 ORDER BY distance_km ASC Stories with real progress bars — The timeupdate event on HTMLAudioElement gives us real-time playback position. We animate the progress bar and waveform display using this data. No fake timers. Voice replies — Being able to reply to a voice post with your own voice changes the entire feel of conversations. It's more intimate, more human, and much harder to be nasty about.
Monetization — Two Revenue Streams OpVoc Pro ($10/month) Implemented with PayPal's JavaScript SDK. On payment capture, we call a PHP endpoint that updates is_pro=1 and sets pro_expires to one month from now. Pro users get 10-minute posts, a badge, and priority feed placement. Sponsored Posts Businesses can promote their audio content with a promo link. We show a "SPONSORED" badge and a "See more →" button that links to their URL. Payment via PayPal, campaign management through the admin dashboard.
The Admin Panel Every social network needs moderation. We built:
User management: ban, shadow ban, mute, delete, grant/remove Pro Content moderation: suppress posts, delete, sort by report count Auto-suppression: 15 community reports triggers automatic hiding Activity logs: every admin action is logged with timestamp Starter posts: admin can post as "OpVoc Team" to seed content for new users Analytics: total users, posts, plays, new users today, new posts today
What I'd Do Differently
Build the mobile experience first. I built desktop-first and adapted for mobile later. Most users come from mobile. Should have been the other way around.
Solve duration without ffprobe earlier. The fallback to duration=0 creates a poor UX when people see "0:00" on posts. I should have found a client-side solution earlier.
Seed content before launch. An empty feed kills conversion. The starter posts feature came too late. New users need to see activity immediately.
Build share mechanics into the core. Making content easy to share to WhatsApp, Twitter, and Telegram from day one would have accelerated growth. We added it, but it should have been there from the start.
Where We Are Now OpVoc is live at opvoc.com. Free to use. Voice posting, stories, nearby feed, anonymous mode, private messages, full social graph — all working. The early users are genuinely engaged. Voice content creates a different kind of connection than text. People come back because they want to hear the people they follow. If you're a developer curious about the stack, or someone interested in the product, I'd love to hear from you. Try it at opvoc.com And if you have thoughts on the technical decisions — especially around audio handling on shared hosting — drop them in the comments. Always looking for better approaches.
OpVoc — The social network for your voice.
No responses yet.