who needs AI when you can just do better (without relying on the greatest copyright infringement scandal in modern human history)

who needs AI when you can just do better (without relying on the greatest copyright infringement scandal in modern human history)

u/Zhuinden — 6 days ago

Tried the Nongshim Shin Cheese Kimchi Ramen, and it sounds a lot better than it tastes

See, you came here expecting a Shin Ramyun from Korea, but this is a Nongshim Shin product from China. So, different rules apply. For example, the fact that the one thing reminding me of Shin in general is the drawing of the original Shin package on the packaging. Now hold onto that and don't look down.

Oh, wait, it's too late, you can't really avoid the smell either can you? Heh.

Anyway, so I opened the package and it's, well, "cheese" and "kimchi", I didn't even open the packets yet but you could somehow smell them. Dubious. Kimchi is known to have a wild scent, but dehydrated kimchi? Really?

And the cheese... You could also smell the cheese. But the cheese is your expectations for budget cheddar cheese powder, you know the one that smells and tastes more processed than cheddar, in those low quality ramens.

Also note that the noodles are clearly more "basic" it doesn't look like Shin style noodles, it's almost a large cup ramen block, it's not bad noodles honestly because KSF also uses the same noodles in their block as their bowls, so it somewhat makes sense if this is a regional thing.

Overall, I ate it, and it surprisingly didn't make me queasy, the vegetable flakes had the kimchi and the soup base had the cheese. So you can't even ditch the cheese once you know what you're getting into. You get a slightly spicy broth, but without the depth of Shin, with some somewhat dubious rehydrated kimchi, and budget cheese powder in the soup base. I bought two due to excitement but clearly I made a mistake.

Buy a Shin Kimchi, that orange package, you don't really need this, surprisingly enough. I'd say I only ate this to help think of myself as a revered taste tester. (Even if it's not on the same level as the ramen rater's bowl of slime.)

u/Zhuinden — 17 days ago

Tried the Acecook Chicken Pho instant noodle soup, and it's surprisingly pho-flavored

I came in with relatively low expectations because whenever I bought instant pho paste, it didn't taste like pho at all, but somehow this thing comes with a flavoring oil that turns your water into pho in no time.

The rice noodles are a little awkward, for a moment they were a bit hard, the next moment they were overcooked. Welp. I didn't find instructions so I just tried my best.

Overall, surprisingly solid. The chicken is chicken powder, pretty much matching expectations.

Makes me kinda dread the Mama variant though, because it's a surprise there's a Mama Pho at all. I'll see when I get there.

Imsgine if they sold this pho oil separately...

u/Zhuinden — 18 days ago

Tried the JML stew beef flavor noodle soup, it's a pretty good beef

It has good flavor, pretty much in line with what you expect from this type of instant ramen. The noodles are a little weaker, they seem a bit Denue-like, but there's nothing wrong with them. I don't see this very often at stores, but I'll try to get it again in the future.

u/Zhuinden — 19 days ago

Tried this new Kang Shi Fu Beef Soup, and just so it happens it's actually spicy beef with vinegar (despite the new packaging)

A new type of Kang Shi Fu packaging, though I wasn't expecting it to be a new variant of the spicy beef with vinegar.

However, it's definitely not 1-to-1 exactly the same, I think the paste itself is a little different, and this one was spicier.

Also I may have added slightly more water than I should have, whoops.

Anyway, it's really hard to go wrong with a Kang Shi Fu Spicy Beef With Mature Vinegar, so obviously this was delicious too.

u/Zhuinden — 23 days ago

Tried the Shin Ramyun Rumi Superstar Instant Cup Ramen, and it's a nice, spicy smoky ham flavor

You probably my attempt at eating Zoey Hamburger and it was insta-bin, well the good news is that Rumi Superstar is actually pretty good.

I mean, it's Shin Cup, so you hypothetically know what you expect, but this is actually a little different! It comes with some veggie flakes, processed ham bits, and these tiny little stars that may be pasta (I'm not sure) but it really does make you feel like a superstar, haha. A nice addition.

Flavor-wise, it's not too spicy, but it does have spice, more importantly if you know the "Samyang Original" instant block, this is VERY similar, so much so that when you rip up the flavor packet you can clearly smell the smoky ham flavoring.

So, you like smoky ham? Good! Then you should get this. This one's worth a try overall, nice cup of Shin Cup with a twist.

*(recap: The hamburger was really bad, the spicy queen was fairly standard, this one's the Samyang OG twist of Shin.)*

u/Zhuinden — 1 month ago

Tried the Shin Ramyun Zoey Hamburger Flavor Instant Cup Ramen, but it's really bad haha

Welp, this was a curious incident honestly, I figured I'll try it out just to know what I'm missing, but I guarantee if you haven't tried it then you're not missing out on anything.

It doesn't taste anything like Shin. In fact, here in Hungary, we have a "Maggi májgombócleves" (liver ball soup, it claims) which has this exact same flavor, except the "hamburger" comes with a bit of chili, a bit more flavor enhancer, and a fairly pungent smell. I swear they wanted to copy a flavor they didn't understand and then shipped it as is.

It comes with these awkward meat balls, but no vegetable flakes, and honestly it has no redeeming qualities, it was so bad I took 1 bite and ditched it. It's really that bad. You can avoid it entirely.

u/Zhuinden — 1 month ago

Tried the A'Kuan Lanzhou Beef Noodle Soup, and it's a mildly spicy, MSG heavy beef with A'Kuan's dense noodles

You know your A'Kuan, place in a bowl, add boiling hot water, cover with a lid, then eat it - don't boil the noodles, they become a sludge if you boil it.

So in this case I made sure not to boil, although this dense noodle (similar to the Chongquin version) is rather dense, a little tricky to eat honestly, I ended up using spoon + fork and was making sure I don't "swallow it wrong" I swear this would kill me if I swallow it wrong.

What did we have in the bag? We have a bit of vegetable flakes, the beef flavor paste, and a bit of chili oil actually with chili flakes, it however didn't bring a szechuan peppercorn vibe (so it's not like the Chongquin Hot And Spicy) it's just a mild spiciness, not too hot really.

Somehow the ready images I feel were missing a bit of mix-up, sorry about that.

I'd say flavor-wise it's solid, no complaints really, good beef soup with mild chili levels, a bit MSG heavy but that's where the flavor is.

Overall, I think it's good, BUT I also think I have various other flavors that I prefer more over this, noodle-wise I think a KSF is drastically less tricky to eat (these noodles get really dense), and the szechuan flavors of the A' Kuan Chongquin go really hard and that's quite unique among the ones I've had so far.

u/Zhuinden — 1 month ago

echoes of a sad reality where you don't starve but you also don't have a soul

u/Zhuinden — 1 month ago

Tried the Kang Shi Fu Tomato Beef Soup, and I hope you like tomato cuz you're getting tomato

They're not kidding! So imagine you're eating good old KSF Braised Beef. Then you woke up and said, what if you added like, a tomato soup, to your beef soup? And you'd think that's definitely an idea otherwise seen as Chinese Borscht (yes I've learned my lesson since the Tang Daren), and you'd be right.

Well, it had a funky smell, but overall I'd say it was quite rich, very interesting to taste the usual KSF beef flavor on top of a tomato soup with somewhat "herb flavor".

I almost wonder, would mixing KSF tomato egg with KSF Braised (or stewed) beef also work?? Though I think that'd make it very salty.

Overall, I've tried quite a few of these odd KSF flavors, this one is a good one I think. It's quite unique, doesn't remind me of much else other than the KSF tomato egg or the TDR borscht, but well, that's reasonable if we consider they're all tomato soups...

I should make note that the most notable flavors really are, your KSF beef, the tomato, and whatever herbs you put in a tomato soup. Honestly I could have added extra tomatoes, it would have worked nicely.

u/Zhuinden — 1 month ago
▲ 91 r/android_devs+1 crossposts

How we rewrote Reddit's video player on Android

Written by Alexey Bykov, Staff Software Engineer at Reddit & Google Developer Expert for Android technology

Reddit serves approximately hundred of millions playbacks a day on Android.
In our last two posts: Improving video playback with ExoPlayer and Taking ExoPlayer Further: Reddit's performance techniques we covered ExoPlayer performance and what you can do to improve startup latency, rebuffering, video quality & stability. (Check them out if you haven't yet, they also show how your production metrics may improve after every optimisation)

But bundling those performance practices into a reusable component with a safe and clear API turned out to be a different challenge. Over time, every new video integration became harder, and we kept finding edge cases that were difficult to support without breaking existing behaviour.

In this article, we'll share how we rewrote our abstraction on top of ExoPlayer from scratch: a better API for teams building video features, and even better performance and stability.

Goal: One obvious way to play video

Our main requirement for the API was simple: engineers integrating video shouldn't need to be video or performance experts. They shouldn't have to think about prefetching, player creation, prewarming, or lifecycle. Video should be fast by default and take a few lines of declarative code to integrate.

ExoPlayer is considered one of the best open-source video players among all platforms. It gives you a dozen valid ways to get to the same playable video, and at our scale, that freedom stops being a feature and starts being a challenge.

Tim Peters put it best in the Zen of Python: "There should be one, and preferably only one, obvious way to do it."

Architecture

Playback engine: ExoKit

To bundle all of the optimisations together, we built an agnostic playback engine called ExoKit.

https://preview.redd.it/7zeheph0a3ch1.png?width=1999&format=png&auto=webp&s=72be5448ce219a028e75822a603b348987e7ad64

It abstracts away ExoPlayer, makes the API stricter, owns all player communication, and keeps every playback in a central place with a single app-wide video state and effects handling.

It supports all of the performance features we covered earlier, such as:

  • Player pooling and prewarming the pool on the app start, since creating a player can still take up to ~200ms according to our production traces
  • Decoder reuse for identical videos, which saves up to ~80ms more
  • Warming up videos during first composition (not to be confused with prefetching), so the first keyframes are decoded and rendered before the user scrolls to the video.

You can read more about these optimisations and their impact on our metrics here.

On top of that, it offers two APIs: a declarative one for rendering and an imperative one for managing playback state.

u/Composable
fun PostVideo(
    mediaId: String,
    url: String,
    modifier: Modifier = Modifier,
) {
    val key = remember { PlaybackKey(mediaId, "feed") }

    // Imperative API: express playback intent.
    val actions = rememberPlaybackActions()
    PlayButton {
        actions.action(key, PlaybackAction.Play)
    }

    // Declarative API: render from playback truth.
    val state = rememberVideoPlaybackState(mediaId)
    if (state.isBuffering()) {
        LoadingIndicator()
    }

    // Declarative API: describe the video surface.
    Video(
        modifier = modifier,
        props = VideoProps(
            url = url,
            playbackKey = key,
        ),
        surfaceLifecycle = rememberLifecycle(),
    )
}

But even though it looks minimalistic enough, it still leaves a lot of responsibility on engineers. They have to decide where to play the video (which gets tricky with autoplay on), manage the player lifecycle, fire telemetry and coordinate between the declarative and imperative APIs.

To make it easier, we landed on a much more opinionated API. 

Self-hosted UI Components

Our first step was to abstract the imperative API from the feature layer: one less decision means one less way to get it wrong. But that logic had to live somewhere.

Rather than building reusable abstractions and inject them into every screen's ViewModel, we went the opposite way: every UI component became fully independent and self-hosted.

https://preview.redd.it/0j7sidx6a3ch1.png?width=1999&format=png&auto=webp&s=750eeccabc33f9602262ccac1eb32b7b9b3f2878

To ensure every composable can access product-related context, such as analytics, every block implements the following contract:

interface Component<Props : Any> {
    u/Composable
    fun Content(props: Props, modifier: Modifier)

    fun key(props: Props): Any = props
}

Implementations live in an impl module and can also access product-related context.
Here is a simplified example of what a lower-level composable might look like inside a component implementation:

u/Composable
// Very simplified version of the code for mute button
fun MuteButton(
    playbackKey: PlaybackKey,
    modifier: Modifier = Modifier,
) {
    // Imperative API
    val playbackActions = rememberPlaybackActions() // manage playback
    val globalActions = rememberGlobalActions() // global state, e.g. settings

    // Declarative API
    val audioSettings by rememberAudioSettings()
    val playbackState by rememberPlaybackState(playbackKey)

    // no sound -> hide, loading -> settings fallback, has sound -> real state
    val muted = when (playbackState.audio) {
        AudioTrackState.HAS_NO_SOUND -> return
        AudioTrackState.UNKNOWN -> !audioSettings.isEnabled(playbackKey.surfaceId)
        AudioTrackState.HAS_SOUND -> playbackState.isMuted
    }

    IconButton(
        modifier = modifier,
        onClick = {
            globalActions.action(
                GlobalAction.SetSurfaceAudioSetting(
                    playbackKey.surfaceId,
                    audioEnabled = muted,
                ),
            )
            playbackActions.action(
                playbackKey,
                PlaybackAction.Mute(!muted),
            )
            // More things, like handling product-related telemetry
        },
    ) {
        Icon(if (muted) Icons.VolumeOff else Icons.VolumeUp)
    }
}

The same pattern applies to every clickable or interactive media component. Every UI component also usually has its own ViewModel.

At the screen level, media becomes a set of declarative components that can be arranged like any other UI:

u/Composable
fun SimpleVideoScreen(
    screenState: SimpleVideoScreenState,
    videoComponent: Component<VideoProps>,
    playComponent: Component<PlayProps>,
    muteComponent: Component<MuteProps>,
    seekbarComponent: Component<SeekbarProps>,
    modifier: Modifier = Modifier,
) {
    val key = screenState.playbackKey

    Box(modifier.fillMaxSize()) {
        // 1. Video
        videoComponent.Content(props = screenState.toMediaProps())

        // 2. Play
        playComponent.Content(props = key.toPlayProps())

        // 3. Mute
        muteComponent.Content(props = key.toMuteProps())

        // 4. Seekbar
        seekbarComponent.Content(props = key.toSeekbarProps())
    }
}

Usually, the Media Foundation team, which focuses full time on media performance and developer experience, implements the components whose internals use both declarative and imperative APIs, whereas feature teams interact with the player through the declarative API.

This new setup gave us three big benefits:

  • Product screens stay simple. They only need to arrange the media components and pass in a few props. Nothing more.
  • Playback works the same way across the whole app. Every component shares the same playback state wired by playback key, actions, settings and telemetry pipeline under the hood, so users get one consistent experience no matter where they are.
  • Performance. Screen-level state does not need to update for every media-related event. Only the affected media component recomposes.

Since introducing a component-based model in our app, AndroidX has made a new addition to their Media3 UI. This new model might serve as a good starting point if you’re thinking of solving similar problems today.  One key difference remains: our media components are tied to global playback state, while Media3 UI’s are tied to one Player. So the challenges still remain the same: player creation and the rest of the lifecycle ownership.

Experimentation Setbacks

Choreographer-based Seekbar

I personally found the seekbar a challenging component to implement smoothly, and there are a few interesting decisions we made.

So what does “smooth” mean in practice?
Basically, don't do more work than the screen can render. A 60Hz display takes roughly 16.7ms per frame; at 120Hz (which is not a rare thing anymore), that reduces to  ~8.3ms to render a frame. And if it goes beyond this limit, the user sees a jank.

This is where Choreographer can help. It runs right before Android draws the next frame, using the display’s real refresh rate. If the previous frame takes too long to draw, it waits for the next frame instead of firing again. That means we do not stack seekbar updates on top of an already-janky UI.

Estimated position or player position?
In our custom seekbar we predict the position by extrapolating from the last update, and only update it if the video is playing.
If your application's use cases are limited to video, ExoPlayer.getCurrentPosition() is a cheap operation, and it already uses an estimated position under the hood.

long elapsedTimeMs = SystemClock.elapsedRealtime() - positionUpdateTimeMs;
long estimatedPositionMs =
    Util.usToMs(positionUs) + (long) (elapsedTimeMs * playbackParameters.speed);

Polling this function will give you a smooth, speed-aware position for free. My recommendation is to still drive the updates using Choreographer and not use an arbitrary delay within a Coroutine or an Handler. 

Reddit's production experience
Moving seekbar updates out of screen-level state and driving them with Choreographer made the full-screen video experience measurably smoother.

On our full video screen, the slow-frame rate dropped by 7.3%.

https://reddit.com/link/1urq7f6/video/ifte8fv9g7ch1/player

Playback error 1004 & MediaSource reuse

While experimenting with our newly written ExoKit in production, there was a noticeable jump in playback error 1004 with "Unknown error" message. It affected a small share of devices and was challenging to reproduce locally. Our initial hypothesis was that it was a device-specific playback issue.

The root cause was our MediaSource cache. In some cases, the same cached MediaSource was reused across different player instances. This is something that I wouldn't recommend doing, as every media source is bound to the playback handler of the player it was originally attached to. Otherwise, you risk hitting an error which is thrown here.

Reddit's production experience
We fixed this by keying cached media sources by player id, so a MediaSource can only be reused by the same player instance.

Another possible fix was to move all playbacks onto a single playback thread. That also avoids the handler mismatch, but our production data showed that it made overall startup performance worse:

  • % Video started in less than 250 ms: −0.199%
  • % Video started in less than 500 ms: −0.132%
  • % Video started in more than 1 sec: +1.165%
  • % Video started in more than 2 sec: +0.634%

Rebuffering problems

If your app saves and restores playback position, be careful. In our old player we found that at least 36% of plays had a very short stall, under one second.

The main reason came from old choices made years ago. The old player restored the saved position as a check on every play and every move between screens. But the saved position did not match the position the player held at run time. Here is why: if you call player.pause() and then save player.position, and later call player.seek(savedPosition), the two numbers may differ by a few milliseconds. player.pause() does not finish right away. If you want the true position, wait for a playback state change first.

Reddit's production experience
We stopped saving positions this way. Instead we lean on the run time cache kept in the media source, and we reuse the player for videos the user watched before. This removed all of these tiny stalls.

Saving position on your own is still useful when the system kills the process of your app and you need to restore it for a long video.
If you cover this case, watch the order of your calls. Always call seek() first and prepare() after. If you do it the other way, the player loads key frames and chunks you don't plan to play.

Trade-off: Only one playable video at a time

Simultaneous playback is possible on Android, but hardware decoders are finite and shared across the whole device. A high-end phone might decode two or three videos at once, a low-end one just a single video, and an app sitting in Picture-in-Picture can quietly hold a decoder you were counting on. When you run out, you either fall back to software decoding (higher CPU usage, dropped frames) or fail playback with errors like 4001 or 4003.

Reddit's production experience
We could have partly mitigated this by checking the device performance class (a challenge in itself, since not all devices support it), but the development and testing cost wasn't worth the benefit. Instead, ExoKit runs a state machine that picks one active playback by priority (at Reddit it's based on how much of the video unit is visible, but it could also be based on how much of the screen's playable zone it fills).

We expected every surface to start faster after the rewrite. But surfaces that went from playing several videos to playing one improved their startup latency even more than the ones that already played a single video in the control group.

Conclusion

Besides an improved developer experience, the rewrite reduced perceived start latency by 65% at P50 and by 20% at P90. (An additional factor which helped here was the removal of a lot of unnecessary IO work from the startup path. In production traces, a single Main → IO dispatch before playback could add up to 11ms at the P99.)

Kudos to Merve Karaman, Ahmed Nawara, Irene Yeh and Vikram Aravamudhan for making this rewrite possible. We used to talk about this as a dream 2-3 years ago, and now it's our new reality.

Thanks to the following folks for helping me review this article: Iaroslav Khramov, Nicholas Ngorok

reddit.com
u/SussexPondPudding — 1 month ago

Tried the Nissin Raoh Yuzu Flavor Ramen, and I was clearly duped into seafood

I was curious what a "yuzu" flavor instant ramen would be like, or maybe yuzushoyu or that sort of thing, but in reality you get the noodles and the flavor oil, the flavor oil has a tiny bit of yuzuness and, uh, not a tiny bit of whatever's in the sea. Fish and clams and shellfish and seaweed(?) sort of thing.

It's also hard for me to tell if I added the right amount of water for this amount of noodles, as I cooked the noodles separately, then put the oil in and added new water. But somehow the flavor came out a bit weak, and even that flavor was mostly seafood.

It should be no surprise, I'm not so much into seafood, so for me this as a choice was kind of a dud. Then again, the package does in fact show fish in that ramen as a topping, so I shouldn't be surprised, lol. On the bright side, it's really just part of the flavor oil, it's nowhere near as "processed seafood" as the laksa was.

Overall, it's a little lackluster to me, because I'm used to stronger flavors, and I thought this would have soy sauce. I guess I could have added my own soy sauce, hmm. I preferred the Tonkatsu Nissin Raoh, and that Tantanmen. And also the green onion oil one. Yeah, I think I liked this one the least, even though it's popular.

u/Zhuinden — 1 month ago

Tried the Kang Shi Fu Beef Bowl (?), and it's really delicious

It's very close to the KSF Braised Beef, so much so that it's hard to tell an actual difference between the two. But I feel like this one might be even a little more balanced. They're both pretty good, they're very close, I think I can safely recommend this to anyone really. Pretty good beef flavor. Not spicy (as in, not hot).

u/Zhuinden — 2 months ago

Tried the Shin Ramen KPDH Mira Spicy Queen Cup Ramen, but it's hard to explain the difference

It's kinda like the Shin Super Spicy except I think the powder has a fuller flavor, it's honestly quite delicious, less paprika heavy, but I genuinely can't really tell you what the dominant flavor is. It really is like a slightly stronger version of the regular Shin (Red). But not ridiculously spicy by Korean measures, which I was a bit worried about (it being an actual Korean product after all). I'd say it's rather balanced, but I can't explain it at all. Onion? Beef? Korean spiciness?

I still have to try the Shin Zoey Hamburger and the Shin Rumi Superstar, hopefully I'll be able to get you a better understanding of the flavor profile next time.

I feel like this one didn't have any of the mushrooms in the flakes that my other Shin cup had, but it was still pretty good.

P.S. : I didn't add any kimchi, but knowing a good kimchi jjigae, it would go really well with this one. OTOKI Jin Spicy is crying that it doesn't have like even 14% of the flavor this one packs with.

P.P.S: I feel like there were a little less noodles weight-wise in this one than in the regular cup, but it was a nice amount as it's way too hot for me to eat a lot atm anyway. Might just be my imagination.

u/Zhuinden — 2 months ago

My brother brought me a Shin Ramen fridge magnet as a souvenir from South Korea

He also brought me a pack of Perilla Seed Powder, which literally nobody sells here anywhere, so the loot rarity level is crazy

u/Zhuinden — 2 months ago

Tried the Baixiang Artificial Mutton Flavor Instant noodle soup, and it's quite spicy, probably the best among the Baixiang tbh

I don't like the Baixiang beef flavor, but thankfully the mutton is pretty nice overall. Expect it to be spicy though, even though it doesn't really say so anywhere. Not "too spicy", but definitely spicier than the usual Chinese spicy beef ones tend to be.

​

So what's it taste like? Kinda like the Chinese spicy beef, except it's mutton. Also it doesn't have that overpowering paprika flavor that spicy beef tends to have. Overall, it's worth a try, although I still prefer Kang Shi Fu and UNIF as Chinese brands.

u/Zhuinden — 2 months ago

Tried the UNIF artificial roasted beef flavor, and surprisingly, roasted isn't braised

So I went in with odd expectations, because due to the red color of the packet, I had some expectation for how this is "UNIF's take on the Kang Shi Fu Braised Beef", but it's not.

​

This actually tastes more like a "not spicy version of the spicy beef", not heavy on paprika at all, not spicy at all, I'd say it's primarily like pepper and.. caraway? Cumin? Fennel? I think people call it fennel.

​

Anyway, so the dominant flavor may be fennel, it really reminds me of a Hungarian soup called "lebbencsleves", I can almost taste the potatoes and the meat of choice (without them even being there).

​

This results in me having two different biases at once, mostly that I was expecting different flavors (KSF Braised), and then I got nostalgia for another soup type (lebbencsleves). So that makes reviewing tricky, my focus should be on the food I'm eating. Regardless, I think it's okay, but it didn't "rock" me in the sense that I'd want to re-buy it, at this moment anyway. I think I liked their stewed pork variant more, oddly enough.

​

However, I really like the UNIF beef sauerkraut (the purple packet) and that one really shouldn't be skipped imo,. UNIF also makes really good milk tea, so I always wonder how their other products fare. They always taste a bit less artificial than the others.

u/Zhuinden — 2 months ago