Earlier today, I posted a build script - the one I'm now using for BottomFeeder runtimes. The cool thing is, that script is easily adapted to any runtime. You just use code like this to load the parcels you need:
parcelList do: [:each | Parcel loadParcelFrom: 'parcelToLoadhere.pcl].
And then drop down to the packaging code that I covered in the post. The cool part is, you really don't need to use RuntimePackager anymore. With a simple Smalltalk script and an accompanying shell/batch file, you can:
- Install your own unhandled exception handler
- Specify the startup code with a subsystem class with a simple API
- Build up from a base runtime, rather than stripping down from a development image
All of that is pretty cool - it has never, ever been easy to work with RuntimePackager. With BottomFeeder, I had to rebuild the list of classes to keep and remove each time there was a new release of VW, and - as I added/refactored code, that list would often need tweaking - as the tool often makes mistakes on its own. With the build up mechanism I wrote about earlier, that just doesn't happen.
Technorati Tags: deployment
I don't know whether to laugh, or cry:
I like the fact that my iPhone will work overseas, but stories like this remind me that outside the US, the only internet browsing I do with it should involve WiFi access:
A hapless Slingbox user managed to run up a data bill of $28,067.31 watching a game of American football, despite being aboard a docked cruise liner and having an unlimited data tariff, thanks to a technical hitch or two.
This poor guy hadn't even left the US yet, but it sounds like it would be very, very easy to run up a huge bill via overseas Data Roaming...
Microsoft has dropped an attempt to recoup some severance money from 25 recently fired workers it mistakenly overpaid. The Redmond, Washington-based company, which announced a plan to cut up to 5,000 jobs in January, acknowledged on Sunday that it had tried to get the overpaid workers to return the extra money. But late on Monday, it reversed course. "This was a mistake on our part," said a Microsoft spokesman in an e-mailed statement. "We should have handled this situation in a more thoughtful manner."
This was nearly guaranteed to go this way once it became public knowledge, and I'm sure that MS' PR folks could have told legal that it was going to go that way, had they been asked. Taking legal action is never simple, but it's even more complex now...
Today's Smalltalk Daily covered the process (with some Mac specifics) - I thought a post on the topic, in addition to the video, might be useful. First, the important part of the build script (the part I'm omitting loads all the required parcels into a base image and sets some application level state. To get the startup class specified, simply create a subclass of UserApplication, and add a #main method that executes your startup code. I covered that here.
"go to deployment" DeploymentOptionsSystem current startInRuntime: true. Notifier current: RuntimePackager.BfRuntimeEmergencyNotifier. Notifier uheFilename: 'error.log'. Notifier logToFile: true. UI.WindowManager noWindowBlock: [:windowManager | ]. stream := WriteStream on: String new. stream nextPutAll: 'changeRequest'; cr; cr; tab. stream nextPutAll: '^true'. VisualLauncher compile: stream contents. VisualLauncher allInstances do: [:each | each closeAndUnschedule. each release]. Workbook allInstances do: [:each | each closeRequest]. (Delay forSeconds: 10) wait. promise := [ObjectMemory permSaveAs: 'bottomFeeder' thenQuit: false] promise. promise value. RuntimeSystem isRuntime ifFalse: [ObjectMemory quit].
What does that do? Well, it sets up custom notifiers for the runtime (I don't want the default behavior that RTP specifies, which is "log and quit") when an unhandled exception happens. Then it turns off the "default window" block - that's the behavior whereby the launcher window pops up if all other windows are closed. Then it closes all Launchers and Workspaces, and saves the runtime image. Note that it does that in a separate process - that's so that the runtime doesn't start up and immediately quit :)
That gives me a runtime. I'd like to make it smaller though (not in memory consumption terms, just in disk space terms). So I use this script:
"load the compression code" [#( '$(VISUALWORKS)/packaging/ImageCompression.pcl' ) do: [:each | Parcel loadParcelFrom: each]] on: MissingParcelSource do: [:ex | ex resume: true]. "now compress the image" ObjectMemory compressImageFile: 'bottomFeeder.im' to: 'bfSmall.im' resultsTo: [:inFileSize :outFileSize :compTime :decompTime| Transcript cr; nextPutAll: 'Image compressed to '; print: (outFileSize * 10000 / inFileSize) rounded / 100.0; nextPutAll: '% of original ('; print: inFileSize->outFileSize; nextPut: $); cr; nextPutAll: 'Deompression + read time '; print: decompTime // 10 / 100.0; nextPutAll: ' seconds (compressed in '; print: compTime // 10 / 100.0; nextPutAll: ' seconds)'; flush]. (Delay forSeconds: 2) wait. ObjectMemory quit.
That fires up a new image, loads the compression parcel, and runs it on my image. Here's the shell script that guides the whole thing - and yes, sleeping to ensure that commands have finished is probably sloppy - if you're better at shell scripting, I'm sure you'll have a better answer there:
#!/bin/sh echo "Running basic image creation..." ./startvw visual.im -fileIn build-bf-non-windows.st sleep 120 echo "Running image compression..." ./startvw visual.im -fileIn compress-bf-image.st sleep 30 echo "Pushing new image into the .app..." cp bfSmall.im macBuildDir/bottomFeeder.app/Contents/Resources/resource.im echo "done"
That copy command drops the image into the Mac App bundle. As it happens, that bundle is just a directory structure, with the VM in one place, the image in another, and an XML file to explain it. That's documented - look in the "packaging" directory in the distro.
Finally, what about specifying your own app image for the dock? Well, in the app bundle is a file called "herald.tiff". Drop your image there in TIFF format, and you should be good to go.
Technorati Tags: deployment
Learning about someone's product doesn't sound like a way you want to spend your valuable time, does it? Stories are exciting. Most marketing is not.
Your job as a marketer is to tell stories that people are eager to share with their friends, colleagues, and family members.
I see a lot of this in marketing circles now, on blogs and on Twitter. It's a nice idea, but there's a very basic problem with it: "telling a story" doesn't move the ball forward, unless that story illustrates something about what your company does. Sure, people love stories, and they'll read entertaining ones whether they're related to what you do or not. What they won't do is get any idea as to what your company does, how it does it, and how you can help them solve their problems.
Stories are great - so long as they are connected to what you do. If they aren't connected, you might as well try to be a novelist.
There's another problem here, too. When I'm searching for solutions to a problem I have, the last thing I want to run across is a meaningless story about something that a marketing department thought would interest me. What I want to find is the answer to my problem. Say I'm looking for help with CSS for my site - is it more helpful for me to find a long winded story about some random topic, or a site that gives me examples of how to solve my problem - along with an offer to help me do it?
Marketing is a conversation, but it's a directed one.
I guess you can't make headlines by claiming that TV rots the brain anymore - the new brain rotting scare is "social media":
Social networking websites are causing alarming changes in the brains of young users, an eminent scientist has warned.
Sites such as Facebook, Twitter and Bebo are said to shorten attention spans, encourage instant gratification and make young people more self-centred
I'm not sure what will be scary twenty years from now, but rest assured - it'll be something :)
Technorati Tags: social media
I've just finished building a one script build for BottomFeeder on the Mac; I'll need to create a batch file to do the same thing on Windows, and adapt the existing script (minor, really) for other Unix/Linux platforms. It's probably of general interest, so I'll do a screencast on it tomorrow.
Everyone has been pointing to this Marc Andreeson interview with Charlie Rose - around minute 28 he gets going about newspapers, and eventually says that the "game is over" for print news. This isn't news to me; I doubt the companies running the papers will pay much attention though. It's really, really hard for a company that's had success in X to transition successfully when things transition over to Y.
The far more likely path is what we're seeing happening already in some of the niche areas, like tech news: the old players are being completely overwhelmed by the new players, because the new guys don't have any romantic connection back to "the way it should be done". I expect that we'll have plenty of good news reporting in the future - it just won't be happening via the same companies who did print. And, in my opinion, radio and tv are going to get the same treatment....
Here's another case where a company is going to wish that legal actions ran by PR first: Microsoft is asking some laid off workers to pay back excessive severance pay:
The software giant, which recently laid off 1,400 employees, sent letters (see image below) [ed: follow the link for that] this week to some of those former workers letting them know that their severance payouts were a bit too "generous" and respectfully requested that the former employees pay back that money, according to a report Saturday on TechCrunch.
Never mind the accounting or tax aspects of this - just consider how that's going to look as it gets publicity. Heck, MS is big enough that this could easily land on a major media site, like Drudge.
As time goes by, I'm more and more convinced that the legal department should report to the PR staff, and not be allowed to do anything before getting a PR blessing...
There was some interesting news last week about Hulu - they stopped distributing with tv.com and Boxee. Now, they could be trying to build an online version of a TV station - like Facebook, trying to get all the clicks to happen on their site. There's also the possibility that cable networks are getting antsy about the whole streaming video thing:
Could it be that the big cable companies are pressuring TV networks and film studios to scale back the content they provide Web services? Peter Kafka from All Things Digital certainly thinks so. Although itâs purely speculation, it's believable, especially given the financial incentive cable networks and operators have to preserve the current cable TV business model. In fact, Glenn Britt, CEO of Time Warner Cable blamed part of the company's $8.16 billion loss in the fourth quarter on Web video services, which have been luring customers away from cable companies.
I have no idea whether that's true or not, but I do think cable operators are at the starting point of a downhill run to pain. Just as the web has been steadily destroying the newspaper business, I think we're starting to see the business model for TV and radio change over. In an on-demand culture, the whole "must see tv" thing just doesn't fit anymore.
I have a few ideas in the queue, and a few suggestions as well - but Smalltalk Daily is for you, not me. So - feel free to hit me with ideas. Send me email, comment here, or heck - hit the talkback widget on the blog!
The logical question you'll have for me is what Cincom is doing with respect to 2.9. Well - we've been heads down on the Web Velocity project, but the first release of that is getting pretty close. We'll be looking at moving our support from 2.8 to 2.9 after that, with formal Cincom support coming when 2.9 is officially released.
In the meantime, why not follow Julian so you can keep track of the project?
Technorati Tags: smalltalk
This week we spoke to Martin Kobetic, one of Michael's fellow developers on Cincom's Smalltalk team. We mostly talked about the security libraries (encryption, etc) in the product, although we spent a bit of time on Opentalk and grid computing as well. To listen, click here.
There are some audio artifacts in Martin's portion of the audio that I just couldn't filter out. I apologize for that; it sounds a bit like crackling on a phone line.
If you have feedback, send it to email@example.com - or visit us on Facebook or Ning - you can vote for the Podcast Alley, and subscribe on iTunes. If you enjoy the podcast, pass the word - we would love to have more people hear about Smalltalk!
Last night's concert was a blast - Paul and Storm were hilarious, and Coulton played a great set. He had Paul and Storm on stage with him a fair amount, and the three of them together were really funny. If they play near you, run, don't walk, to get tickets.
Of course, I forgot to bring my good camera :) But I did have my iPhone handy:
And thanks to Mike and Jo - the tickets and dinner were a fantastic birthday present!
This morning my Mac decided to act like a Windows box - a bunch of applications froze up (Mail, iPhoto, iTunes) - and the only way to unzombie them was a reboot. And that required a hard power off, because shutdown wasn't able to kill them, either.
Strange. I like the Mac a lot, and it's a huge, huge improvement over Windows - but it's not a perfect platform, regardless of what the fanboys would have you believe :)
I'm heading down to Annapolis to see Jonathan Coulton with friends - best present ever, these tickets :)
AppleInsider has some sobering news about the App Store for the iPhone:
The vast majority of apps downloaded from the App Store are in use by less than 5% of users after one month has passed since the download, according to an analytics firm that is also shedding light on other aspects of the business.
I can't speak to that personally - I'm less than a month into my ownership, and some of the apps I have (Yelp and iWant) will be most useful when I'm on the road in an unfamiliar place - so it's hard to say much about those right now. It does mean that TANSTAAFL holds as much for the iPhone app developer as it does for anything else :)
Thus far I've been very pleased with the iPhone. Battery life is good so long as I limit my use of 3g (and, as it happens, most places I go have WiFi anyway). Browsing the web on the phone is actually pleasant, and I've already installed a decent stable of apps. After some initial worrying about network coverage for straight phone use, I'm pleasantly surprised: the AT&T network seems to be good where I live. We'll have to see how that goes when I travel, of course - but so far, no worries.
Technorati Tags: iPhone
PCWorld reports that Ballmer is chiding Apple for not being open enough:
In what only can possibly be the most recent of Microsoft CEO Steve Ballmer's continuing series of pearls of wisdom, the Rambler from Redmond told a panel at the World Mobile Congress in Barcelona earlier this week: "I agree that no single company can create all the hardware and software. Openness is central because it's the foundation of choice."
Maybe I should have tagged this post as humor; I'm sure there's a joke in there somewhere...
One of the things that sticks to Smalltalk (unfairly now) is the idea that building and deploying a runtime is hard. In this post, I'll take a small application and demonstrate how to go from "code in the environment" to a runtime application. If you want to use this sample application, simply go to the public store repository and install ITunesAlarmClock. It doesn't have any outside pre-reqs. First, here's a snapshot of the simple iTunes alarm clock application:
The application is simple - specify a time, set an alarm, and when the time rolls around, iTunes will play at the specified volume. Here's the way we get the runtime to start the application up when the image starts:
We create a subclass of UserApplication and add a #main method. In #main, we put our startup code. That's most of what we needed. Next, there's a short script to build the image:
| promise stream | Parcel loadParcelFrom: 'ITunesAlarmClock.pcl'. DeploymentOptionsSystem current startInRuntime: true. Notifier current: AlarmClock.EmergencyHandler. Notifier uheFilename: 'error.log'. Notifier logToFile: true. UI.WindowManager noWindowBlock: [:windowManager | ]. stream := WriteStream on: String new. stream nextPutAll: 'changeRequest'; cr; cr; tab. stream nextPutAll: '^true'. VisualLauncher compile: stream contents. VisualLauncher allInstances do: [:each | each closeRequest]. (Delay forSeconds: 10) wait. promise := [ObjectMemory permSaveAs: 'alarmClock' thenQuit: false] promise. promise value. RuntimeSystem isRuntime ifFalse: [ObjectMemory quit].
Adapting that script to your own needs should be straightforward. Here's what's going on there (the file is called configureAlarmImage.st):
- We tell the runtime system that we'll be in deployment
- We specify that the emergency handler to use is AlarmClock.EmergencyHandler
- We tell the WindowManager not to bring up the launcher when there are no other windows open
- The streaming code is a bit of trickery to tell the launcher to not prompt us when we close it in the next couple of lines.
- Then we save the image, with a delay - that's to ensure that the resulting image doesn't start up with the launcher visibly closing.
- Finally, we quit the image we're in, having created a new one
Now, to use that we start the image on the command line. The Mac shell script I use is below; on Linux/Unix you would do something similar. On Windows, a simple batch file would suffice. Heck, you can even just type the command in at the command line:
#!/bin/sh ./startvw ~/Applications/vw7.6/preview/packaging/base.im -fileIn configureAlarmImage.st
That starts up the base image (which does not have development tools in it), loads our code and configures the image (as explained above), and then saves it.
That gives us an image that will start up with our application running. Finally, you need to put this into a platform specific runtime. On Windows, follow the directions here to create an executable. On Mac, follow the directions here. If you want to create a nice DMG file, just use the Disk Utility application to build that. On Unix/Linux, all you need to do is change the image to have executable permissions, and put it and the VM in the same directory. The image can then be run as if it were a schell script.
And that's it! Not a ton of steps, and it's all automatable.
Have a listen to this short bit from the Pandora folks. Why would you pay for Satellite radio when you can stream a custom station through your home network via WiFi, and via 2g or 3g in the car? I've found new music this way, so that argument is dead, too.
It's not just satellite radio, either - other than for local news/weather, terrestrial radio is doomed as well. I think talk radio will move towards live streaming as well - it'll allow the hosts to cut out a whole range of middlemen whose value is rapidly approaching zero.
Technorati Tags: radio
Last week Mozilla released Bespin, their web-based framework for code editing, and only a few days later Boris Bokowski and Simon Kaegi implemented an Eclipse-based Bespin server using headless Eclipse plug-ins. With the presentation of the web-based Eclipse workbench at EclipseCon and the release of products like Heroku, a web-based IDE and hosting environment for RoR apps, it seems that web-based IDEs might soon become mainstream
The idea of having the toolset live in the browser is getting to be mainstream - and we're getting closer to release status with Web Velocity. Want to know more? Check out Michael's latest video.
I was listening to a talk show while I was out doing some yard work, and I heard the host making the argument that sub-standard products often win, based on marketing efforts. His examples were the ones we've all heard: Beta vs. VHS, DOS vs. Mac (et. al.), LCD vs. Plasma. I could add Smalltalk vs. C++ or Java, too. The problem is, you have to really consider the term "best", and how the buyers of the product rank things:
- Beta vs. VHS: early on, VHS had double the recording capability. That mattered a lot if you wanted to, say, turn on the VCR at 8, go out, and be sure that you captured the entire schedule on your favorite channel
- DOS vs. everything else: Price was the killer. When I bought my first PC, I really wanted a Mac - but I couldn't afford the markup (over $2K difference at the time). The market chose price over functionality. As time went on, the sheer size of the DOS/Windows world made for more functionality, too.
- LCD vs. Plasma: You can argue about better picture all you want, but presentation matters - and in the store, the brighter LCD screen counts as "better".
What about Smalltalk? Well, I started teaching Smalltalk classes back in 1992. You have to keep something basic in mind: the base image back then consumed 6 MB of RAM. On today's systems that ship with 2-4 GB, the slightly larger 9 MB is trivial, but back then, 1-2 MB was common, and 8 MB was a loaded system. On a loaded system, running the Windows version of Smalltalk took over the machine. Sure, you were vastly more productive than C++, but could you deploy your application - would it run on the typical end user system of the day?
Things have changed for Smalltalk, fortunately. Today, there are vast reams of memory available on desktops, and Smalltalk is, well, small compared to the typical Java system. The old problems Smalltalk had are long gone, and now it's mostly a matter of getting past the old perceptions. Based on Cincom's profitability in Smalltalk, I'd say we are doing well there.
My main point is this: before you make the claim that the "better product lost", look at the winning product with the eyes of the people who bought the other one. It's probably the case that they're ranking something the second product does better much, much more highly than you expected.
Technorati Tags: sales
I see that a bunch of newspapers are banding together in regional compacts to share content:
The latest iteration of the new content-sharing model brings together The Record of Hackensack, New Jersey, The Star-Ledger of Newark, the Times Union of Albany, the Buffalo News, and New York Daily News, which apparently organized the consortium.
I didn't realize it, but the WashPo and the Baltimore Sun were already doing this. Sounds like a reasonable cost cutting measure, so why am I skeptical? Well, it boils down to what I wrote here - newspapers are trying to be generalists in an era of niche specialization. I just don't know that there's a market niche available for the "we carry it all" business. Online news isn't just driving paper out of news - it's driving hyper-specialization.
Earlier today I posted a bunch of tweets where I expressed skepticism about the idea of storytelling as it relates to marketing. I decided that I can't really expand on the idea in the 140 character Twitter universe though, so here it is:
Stories are good, but only if they are connected to the product or service that you're attempting to promote.
It's not that people don't enjoy a good story; they do. It's that if your story isn't related to what you're working on, it doesn't really get you anywhere. Let me explain that with an example - have you ever watched an ad on TV, and then sat back and asked the room: "What product was that for?"
Those ads are often celebrated by Madison Avenue types, but the dirty secret is this: they represent an expensive failure. If no one knows what the ad was promoting, the company that paid for it might as well have lit a stack of hundred dollar bills on fire. It would cost less money, and might have qualified as an amusing stunt :)
Disconnected stories suffer from the same problem. You might get praise, you might get readers - but you won't get prospects. Why? Simple - no one reading your disconnected story will relate it to what your company does, and a decent proportion of the people who read your disconnected story aren't interested in what your company does anyway. The raw number of readers just isn't that important - the number of readers trying to solve a problem your company can help with is. You don't want to get overly excited by the first number and lose track of the second one.
The important thing here is to keep your eye on the ball. The ball is "promote your products and services", and yes - stories are good. Those stories have to be related to your products and/or services though. If they aren't, you might as well be off writing the "Great American Novel".
Technorati Tags: pr
When a company rolls out a concept car that has an associated iPhone app that controls it, you know that you're looking at a product that's gone from viral to mainstream:
At the touch of an iPhone app, the streamlined rear end of the one-seater pops up to make room for two more people. The adjustable rear end conserves energy by maximizing aerodynamics. The idea, company founder Frank Rinderknecht says, is to create lightweight, streamlined and efficient zero-emissions "individual mobility" that can adapt to suit the driver's needs. The iPhone controls everything from the canopy - there are no doors - to the ignition.
One of the knocks Smalltalk sometimes takes is the "doesn't play well with others" thing - the notion that it;s a world unto itself. I did a screencast showing how easy it is to hook ObjectStudio and iTunes up this morning - I thought a short post on it might be a worthwhile follow-up.
If you're working on a Mac, pretty much everything can be driven with AppleScript. On Windows, COM serves the same role. So if you fire up ObjectStudio, you can jump straight into that world of APIS by loading the OLE support:
Once you've done that, you can open a browser and define a new class. I just created one in the ObjectStudio namespace, descended from Object, and added one instance variable: 'dispatcher'. The example talks to iTunes (the Windows version). The #initialize method looks like this:
initialize "Initialize a newly created instance. This method must answer the receiver." | ole | super initialize. ole := OLEObject newProgId: 'iTunes.Application'. dispatcher := ole dispatcher.
That 'dispatcher' object can now invoke various APIs that iTunes understands. So I created a workspace script to try things out:
| model | model := ITunesModel new. model setVolume: 100. model play. (Delay forSeconds: 10) wait. model pause. model nextTrack. model play. (Delay forSeconds: 10) wait. model pause. model release.
That sets the volume (a property), navigates to the next track, plays, and then pauses. The #release at the end does this:
release super release. dispatcher release
Since COM uses external Windows objects, we want to ensure that they get released. I'll be adding a simple ObjectStudio GUI to this tomorrow - stay tuned to Smalltalk Daily for that!
The important take-away here is this: using ObjectStudio 8, you get all the power and productivity of Smalltalk (including all of the VisualWorks libraries) - and you still get to play natively with the Windows APIs. That's why it's Vista Certified.
I'd love to know what's driving this small trend:
Last month we reported a potentially important change in search-market share trends: After years of steady decline, Yahoo's domestic share numbers (YHOO) had increased for five months in a row. Well, the good news continued in January, with Yahoo's share jumping a half-point to 21%, per comScore.
I've fallen into the "Google is the default" behavior pattern, mostly because I use Firefox, and that's the default choice there. Yahoo isn't really the default choice anywhere, so this kind of rise has to be related to positive choices being made. The numbers are small, but interesting nevertheless.
Hat tip Guy Kawasaki.
Dare Obasanjo wades into the Facebook privacy issue with some words of wisdom:
At the end of the day, many people would like to use technology to solve what is essentially a social problem instead of adjusting their behavior. The bottom line is that even though it is technically possible for Facebook to delete my private messages from your inbox when I decide to delete my account, it would be harmful to your user experience AND it doesn't buy me anything since you've already seen the content. The real solution is for me not to have sent any messages to you that I'll later regret in the first place.
Even if you can un-send or delete content, you can't possibly delete all of the memories of it. I've had long conversations with my daughter about this: the stuff she and her friends put on Facebook over the next few years may well come back to haunt them when they hit my age. We had it simpler: no one was recording our youthful indiscretions.
Technorati Tags: social media
On today's Smalltalk Daily, we take a look at how to use COM from ObjectStudio to drive a Windows application. Since I've done something similar on the Mac using AppleScript, I thought I'd recapitulate the work using COM and ObjectStudio. To watch, click on the image below:
You can also watch it on Vimeo:
Or on YouTube:
Facebook has given up on the controversial new terms of service and goe back to the old one:
Well, that was pretty fast. Facebook has reverted to its prior terms of service -- due to a backlash from some users, media outlets and privacy groups -- while it works out a new version.
The funny thing about being a community driven company is that you have to pay heed to the community. Facebook realized that, and did the right thing here. MInd you, "the right thing" means that they listened, and realized that they have to be more transparent about this kind of thing in the future. There's a lesson there for all of us.
|I finished reading a very timely book last night: Niall Ferguson's "The Ascent of Money". It's a great primer on the basics of money and lending - if you're a novice in that field (like me) trying to understand some of the reporting you see on CNBC, it's a great place to start.|
Not only is it timely (he finished it in May of 2008, just as some of the credit mess was starting to really bubble), but it's written for the layman. Highly recommended.
Today's Smalltalk Daily covers a basic "Getting Started" question: how do you load components into Cincom Smalltalk? After watching this (click on the image below), you should follow up with this screencast, to get an idea about what components you should load to make the environment easier to use.
You can also watch on Vimeo:
Or on YouTube: